--- AWSTemplateFormatVersion: 2010-09-09 Description: Provides nesting for required stacks to deploy a full sample web application with reverse proxy, ALBs, IAM, and other resources (for demonstration/POC/testing) (qs-1r6f0fdce) Metadata: Stack: Value: 3 VersionDate: Value: 20160510 Identifier: Value: template-application Input: Description: VPC, SubnetIDs, S3 bucket names, CIDR blocks, KeyNames, AMIs, DB name and password Output: Description: Outputs ID of all deployed resources AWS::CloudFormation::Interface: ParameterGroups: - Label: default: Availability Zone selection for AutoScaling Group Parameters: - AvailabilityZoneA - AvailabilityZoneB - Label: default: Network (existing VPC config) Parameters: - ProductionCIDR - ManagementCIDR - ProductionVPC - DMZSubnetA - DMZSubnetB - AppPrivateSubnetA - AppPrivateSubnetB - Label: default: Application Configuration Parameters: - WebInstanceType - WebServerAMI - AppInstanceType - AppAmi - EC2KeyPair - Environment - SupportsGlacier - Label: default: Logging configuration Parameters: - CentralLogBucket # - WAFlogging # - Label: # default: Amazon Elasticsearch configuration # Parameters: # - ESClusterARN - Label: default: Database Configuration Parameters: - DBHost - DBName - DBUser - SecretKeyName - Label: default: AWS Quick Start Configuration Parameters: - QSS3BucketName - QSS3BucketRegion - QSS3KeyPrefix ParameterLabels: AvailabilityZoneA: default: ID of first availability zone for AutoScaling Group AvailabilityZoneB: default: ID of second availability zone for AutoScaling Group ProductionCIDR: default: CIDR block of production VPC ManagementCIDR: default: CIDR block of management VPC ProductionVPC: default: Production VPC ID DMZSubnetA: default: First production DMZ subnet DMZSubnetB: default: Second production DMZ subnet AppPrivateSubnetA: default: First production application subnet AppPrivateSubnetB: default: Second production application subnet WebInstanceType: default: Web instance type WebServerAMI: default: Web server SSM AMI parameter AppInstanceType: default: Application instance type AppAmi: default: Application server SSM AMI parameter EC2KeyPair: default: EC2 key pair Environment: default: Environment type SupportsGlacier: default: Supports glacier CentralLogBucket: default: Central S3 logging bucket # WAFlogging: # default: WAF logging # ESClusterARN: # default: ElasticSearch cluster ARN DBHost: default: Database host DBName: default: Database name DBUser: default: Database user QSS3BucketName: default: Quick Start S3 bucket name QSS3BucketRegion: default: Quick Start S3 bucket Region QSS3KeyPrefix: default: Quick Start S3 key prefix Parameters: # WAFlogging: # Type: String # Description: The storage location for AWS WAF logs. Choose Amazon Elasticsearch_S3 to have AWS WAF logs streamed to Amazon ES (current Region) and your central logging bucket. # AllowedValues: # - Amazon Elasticsearch_S3 # - Amazon S3 Only # Default: Amazon S3 Only ruleAction: Type: String Description: The type of action you want to iplement for the rules in this set. Valid options are COUNT or BLOCK. AllowedValues: - BLOCK - COUNT Default: COUNT CentralLogBucket: Type: String Description: The S3 bucket to send AWS WAF logs to. This bucket should already exist. Default: '' # ESClusterARN: # Type: String # Description: (If Amazon Elasticsearch_S3 is chosen for the WAFlogging parameter) The Amazon Resource Name (ARN) of the Amazon ES domain that Kinesis Data Firehose delivers data to. Cluster must be in same account and Region. # Default: '' EC2KeyPair: Description: Key Name for Instance Type: AWS::EC2::KeyPair::KeyName ProductionCIDR: Description: Production VPC CIDR Type: String Default: 10.100.0.0/16 ManagementCIDR: Description: Management VPC CIDR Type: String Default: 10.10.0.0/16 ProductionVPC: Description: Production VPC Type: AWS::EC2::VPC::Id DMZSubnetA: Description: DMZ Subnet A Type: AWS::EC2::Subnet::Id DMZSubnetB: Description: DMZ Subnet B Type: AWS::EC2::Subnet::Id AppPrivateSubnetA: Description: AppPrivateSubnetA Type: AWS::EC2::Subnet::Id AppPrivateSubnetB: Description: AppPrivateSubnetB Type: AWS::EC2::Subnet::Id WebInstanceType: Description: Instance type for the webservers Type: String Default: m5.large AppInstanceType: Description: Instance type for the app webservers Type: String Default: m5.large AvailabilityZoneA: Description: AvailabilityZone A for AutoScaling Group Type: AWS::EC2::AvailabilityZone::Name AvailabilityZoneB: Description: AvailabilityZone B for AutoScaling Group Type: AWS::EC2::AvailabilityZone::Name WebServerAMI: Type: 'AWS::SSM::Parameter::Value' Default: '/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2' AppAmi: Type: 'AWS::SSM::Parameter::Value' Default: '/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2' DBHost: Description: Amazon RDS MySQL endpoint Type: String DBName: Description: Name of RDS Database Type: String Default: examplewordpress DBUser: Description: Username of DB Instance Type: String Default: admin SecretKeyName: Description: Secret Key Name for DB Password Type: String Environment: Description: Environment type (development, test, or production) Type: String Default: development SupportsGlacier: Description: Determines whether this region supports Glacier (passed in from main template) Type: String Default: 'true' QSS3BucketName: AllowedPattern: ^[0-9a-zA-Z]+([0-9a-zA-Z-.]*[0-9a-zA-Z])*$ ConstraintDescription: Quick Start bucket name can include numbers, lowercase letters, uppercase letters, periods (.), and hyphens (-). It cannot start or end with a hyphen (-). Default: aws-quickstart Description: S3 bucket name for the Quick Start assets. Quick Start bucket name can include numbers, lowercase letters, uppercase letters, and hyphens (-). It cannot start or end with a hyphen (-). Type: String QSS3BucketRegion: Default: 'us-east-1' Description: The AWS Region where the Quick Start S3 bucket (QSS3BucketName) is hosted. When using your own bucket, you must specify this value. Type: String QSS3KeyPrefix: AllowedPattern: ^[0-9a-zA-Z-/]*$ ConstraintDescription: Quick Start key prefix can include numbers, lowercase letters, uppercase letters, hyphens (-), and forward slash (/). Default: quickstart-korea-isms-p/ Description: S3 key prefix for the Quick Start assets. Quick Start key prefix can include numbers, lowercase letters, uppercase letters, hyphens (-), and forward slash (/). Type: String Mappings: elbMap: ap-northeast-1: ELB: '582318560864' ap-northeast-2: ELB: '600734575887' ap-south-1: ELB: '718504428378' ap-southeast-1: ELB: '114774131450' ap-southeast-2: ELB: '783225319266' ca-central-1: ELB: '985666609251' eu-central-1: ELB: '054676820928' eu-west-1: ELB: '156460612806' eu-west-2: ELB: '652711504416' sa-east-1: ELB: '507241528517' us-east-1: ELB: '127311923021' us-east-2: ELB: '033677994240' us-gov-west-1: ELB: '048591011584' us-west-1: ELB: '027434742980' us-west-2: ELB: '797873946194' Conditions: ActionCondition: !Equals [!Ref ruleAction, 'BLOCK'] UsingDefaultBucket: !Equals [!Ref QSS3BucketName, 'aws-quickstart'] SupportsGlacier: !Equals - !Ref SupportsGlacier - 'true' Resources: S3ELBAccessLogs: Type: AWS::S3::Bucket DeletionPolicy: Retain Properties: AccessControl: Private PublicAccessBlockConfiguration: BlockPublicAcls: true BlockPublicPolicy: true IgnorePublicAcls: true RestrictPublicBuckets: true BucketEncryption: ServerSideEncryptionConfiguration: - ServerSideEncryptionByDefault: SSEAlgorithm: AES256 LifecycleConfiguration: Rules: - Id: Transition90daysRetain7yrs Status: Enabled ExpirationInDays: 365 Transition: TransitionInDays: 90 StorageClass: !If - SupportsGlacier - GLACIER - STANDARD_IA VersioningConfiguration: Status: Enabled S3AccessLogsPolicy: Type: AWS::S3::BucketPolicy DeletionPolicy: Retain Properties: Bucket: !Ref S3ELBAccessLogs PolicyDocument: Version: 2008-10-17 Statement: - Sid: ELBAccessLogs20130930 Effect: Allow Resource: !Sub arn:${AWS::Partition}:s3:::${S3ELBAccessLogs}/Logs/AWSLogs/${AWS::AccountId}/* Principal: AWS: !FindInMap - elbMap - !Ref AWS::Region - ELB Action: - s3:PutObject SecurityGroupWeb: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: Security group for Reverse Proxy in DMZ VpcId: !Ref ProductionVPC SecurityGroupIngress: - IpProtocol: tcp FromPort: 443 ToPort: 443 CidrIp: 0.0.0.0/0 - IpProtocol: tcp FromPort: 80 ToPort: 80 CidrIp: 0.0.0.0/0 SecurityGroupEgress: - IpProtocol: tcp FromPort: 80 ToPort: 80 CidrIp: 0.0.0.0/0 - IpProtocol: tcp FromPort: 443 ToPort: 443 CidrIp: 0.0.0.0/0 Tags: - Key: Name Value: sg-reverse-proxy-dmz - Key: Environment Value: !Ref Environment SecurityGroupWebInstance: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: Security group for Reverse Proxy Instances in DMZ VpcId: !Ref ProductionVPC SecurityGroupIngress: - IpProtocol: tcp FromPort: 443 ToPort: 443 CidrIp: !Ref ProductionCIDR - IpProtocol: tcp FromPort: 80 ToPort: 80 CidrIp: !Ref ProductionCIDR - IpProtocol: tcp FromPort: 22 ToPort: 22 CidrIp: !Ref ManagementCIDR SecurityGroupEgress: - IpProtocol: tcp FromPort: 80 ToPort: 80 CidrIp: 0.0.0.0/0 - IpProtocol: tcp FromPort: 443 ToPort: 443 CidrIp: 0.0.0.0/0 - IpProtocol: udp FromPort: 123 ToPort: 123 CidrIp: !Ref ProductionCIDR Tags: - Key: Name Value: sg-reverse-proxy-dmz-instances - Key: Environment Value: !Ref Environment SecurityGroupApp: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: Security group for Appservers ALB VpcId: !Ref ProductionVPC SecurityGroupIngress: - IpProtocol: tcp FromPort: 443 ToPort: 443 CidrIp: !Ref ProductionCIDR - IpProtocol: tcp FromPort: 80 ToPort: 80 CidrIp: !Ref ProductionCIDR SecurityGroupEgress: - IpProtocol: tcp FromPort: 443 ToPort: 443 CidrIp: 0.0.0.0/0 - IpProtocol: tcp FromPort: 80 ToPort: 80 CidrIp: 0.0.0.0/0 Tags: - Key: Name Value: sg-app-server-elb - Key: Environment Value: !Ref Environment SecurityGroupAppInstance: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: Security group for Appserver Instances VpcId: !Ref ProductionVPC SecurityGroupIngress: - IpProtocol: tcp FromPort: 443 ToPort: 443 CidrIp: !Ref ProductionCIDR - IpProtocol: tcp FromPort: 80 ToPort: 80 CidrIp: !Ref ProductionCIDR - IpProtocol: tcp FromPort: 22 ToPort: 22 CidrIp: !Ref ManagementCIDR SecurityGroupEgress: - IpProtocol: tcp FromPort: 80 ToPort: 80 CidrIp: 0.0.0.0/0 - IpProtocol: tcp FromPort: 443 ToPort: 443 CidrIp: 0.0.0.0/0 - IpProtocol: udp FromPort: 123 ToPort: 123 CidrIp: !Ref ProductionCIDR - IpProtocol: tcp FromPort: 3306 ToPort: 3306 CidrIp: !Ref ProductionCIDR Tags: - Key: Name Value: sg-app-server-elb-instances - Key: Environment Value: !Ref Environment SecurityGroupRDS: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: Port 3306 database for access VpcId: !Ref ProductionVPC SecurityGroupIngress: - IpProtocol: tcp FromPort: 3306 ToPort: 3306 SourceSecurityGroupId: !Ref SecurityGroupAppInstance Tags: - Key: Name Value: sg-database-access - Key: Environment Value: !Ref Environment WebContentBucket: Type: AWS::S3::Bucket Properties: AccessControl: Private PublicAccessBlockConfiguration: BlockPublicAcls: true BlockPublicPolicy: true IgnorePublicAcls: true RestrictPublicBuckets: true BucketEncryption: ServerSideEncryptionConfiguration: - ServerSideEncryptionByDefault: SSEAlgorithm: AES256 LifecycleConfiguration: Rules: - Id: Transition90daysRetain7yrs Status: Enabled ExpirationInDays: 365 Transition: TransitionInDays: 90 StorageClass: !If - SupportsGlacier - GLACIER - STANDARD_IA VersioningConfiguration: Status: Enabled DeletionPolicy: Delete WebContentS3Policy: Type: AWS::S3::BucketPolicy DependsOn: WebContentBucket Properties: Bucket: !Ref WebContentBucket PolicyDocument: Statement: - Sid: EnforceSecureTransport Action: s3:* Effect: Deny Principal: '*' Resource: !Sub arn:${AWS::Partition}:s3:::${WebContentBucket} Condition: Bool: aws:SecureTransport: 'false' - Sid: EnforceEncryptionOnPut Effect: Deny Principal: '*' Action: s3:PutObject Resource: !Sub arn:${AWS::Partition}:s3:::${WebContentBucket}/* Condition: StringNotEquals: s3:x-amz-server-side-encryption: AES256 ALBApp: Type: AWS::ElasticLoadBalancingV2::LoadBalancer DependsOn: - S3ELBAccessLogs - SecurityGroupApp - S3AccessLogsPolicy Properties: LoadBalancerAttributes: - Key: access_logs.s3.enabled Value: "true" - Key: access_logs.s3.bucket Value: !Ref S3ELBAccessLogs - Key: access_logs.s3.prefix Value: Logs - Key: routing.http2.enabled Value: "false" Name: ProxyALB Scheme: internal SecurityGroups: - !Ref SecurityGroupApp Subnets: - !Ref AppPrivateSubnetA - !Ref AppPrivateSubnetB Tags: - Key: Name Value: ProxyALB - Key: Environment Value: !Ref Environment Type: application ALBappHTTPlistener: Type: "AWS::ElasticLoadBalancingV2::Listener" Properties: Port: 80 Protocol: "HTTP" LoadBalancerArn: !Ref ALBApp DefaultActions: - Type: "redirect" RedirectConfig: Protocol: "HTTPS" Port: "443" Host: "#{host}" Path: "/#{path}" Query: "#{query}" StatusCode: "HTTP_301" ALBWeb: Type: AWS::ElasticLoadBalancingV2::LoadBalancer DependsOn: - S3ELBAccessLogs - SecurityGroupWeb - S3AccessLogsPolicy Properties: Name: ProxyWeb Subnets: - !Ref DMZSubnetA - !Ref DMZSubnetB LoadBalancerAttributes: - Key: access_logs.s3.enabled Value: "true" - Key: access_logs.s3.bucket Value: !Ref S3ELBAccessLogs - Key: access_logs.s3.prefix Value: Logs - Key: routing.http2.enabled Value: "false" SecurityGroups: - !Ref SecurityGroupWeb Type: application Tags: - Key: Name Value: ProxyWeb - Key: Environment Value: !Ref Environment ALBwebHTTPlistener: Type: "AWS::ElasticLoadBalancingV2::Listener" Properties: Port: 80 Protocol: "HTTP" LoadBalancerArn: !Ref ALBWeb DefaultActions: - Type: "redirect" RedirectConfig: Protocol: "HTTPS" Port: "443" Host: "#{host}" Path: "/#{path}" Query: "#{query}" StatusCode: "HTTP_301" # ALBwebHTTPSlistener: # Type: AWS::ElasticLoadBalancingV2::Listener # Properties: # DefaultActions: # - Type: forward # TargetGroupArn: !Ref WebTargetGroup # AlpnPolicy: # - String # Certificates: # - WebTargetGroup # LoadBalancerArn: !Ref ALBWeb # Port: 443 # Protocol: HTTPS # SslPolicy: ELBSecurityPolicy-2016-08 S3AssetsBucket: Type: AWS::S3::Bucket DeletionPolicy: Retain Properties: AccessControl: Private PublicAccessBlockConfiguration: BlockPublicAcls: true BlockPublicPolicy: true IgnorePublicAcls: true RestrictPublicBuckets: true BucketEncryption: ServerSideEncryptionConfiguration: - ServerSideEncryptionByDefault: SSEAlgorithm: AES256 S3AssetsBucketPolicy: Type: AWS::S3::BucketPolicy DependsOn: S3AssetsBucket Properties: Bucket: !Ref S3AssetsBucket PolicyDocument: Statement: - Sid: EnforceSecureTransport Action: s3:* Effect: Deny Principal: '*' Resource: !Sub arn:${AWS::Partition}:s3:::${S3AssetsBucket} Condition: Bool: aws:SecureTransport: 'false' - Sid: EnforceEncryptionOnPut Effect: Deny Principal: '*' Action: s3:PutObject Resource: !Sub arn:${AWS::Partition}:s3:::${S3AssetsBucket}/* Condition: StringNotEquals: s3:x-amz-server-side-encryption: AES256 PreProcInstanceRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - ec2.amazonaws.com Action: - sts:AssumeRole Path: / Policies: - PolicyName: PreProcPermissions PolicyDocument: Version: 2012-10-17 Statement: - Sid: UploadServerCertificate Effect: Allow Action: - s3:PutObject Resource: !Sub arn:${AWS::Partition}:s3:::${S3AssetsBucket}/* - Sid: SelfDestruct Effect: Allow Action: - ec2:TerminateInstances Resource: - '*' PreProcInstanceProfile: Type: AWS::IAM::InstanceProfile Properties: Path: / Roles: - !Ref PreProcInstanceRole PreProcInstance: Type: AWS::EC2::Instance Properties: KeyName: !Ref EC2KeyPair ImageId: !Ref WebServerAMI InstanceType: !Ref AppInstanceType IamInstanceProfile: !Ref PreProcInstanceProfile SubnetId: !Ref AppPrivateSubnetA SecurityGroupIds: - !Ref SecurityGroupAppInstance UserData: Fn::Base64: !Sub | #!/bin/bash -xe echo Force S3 to use Sigv4 aws configure set default.s3.signature_version s3v4 echo Configure the region, necessary especially for GovCloud aws configure set region ${AWS::Region} echo Generating private certificate... sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /tmp/common.key -out /tmp/common.crt -subj "/C=US/ST=Washington/L=Seattle/O=NonProductionTestCert/CN=NonProductionTestCert" echo Uploading key to assets bucket... aws s3api put-object --bucket ${S3AssetsBucket} --key ssl/common.key --body /tmp/common.key --server-side-encryption AES256 echo Uploading cert to assets bucket... aws s3api put-object --bucket ${S3AssetsBucket} --key ssl/common.crt --body /tmp/common.crt --server-side-encryption AES256 echo Signal for success /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource PreProcInstance --region ${AWS::Region} echo Sleeping for 2 minutes to allow CloudFormation to catch up sleep 120 echo Self-destruct! aws ec2 terminate-instances --instance-id $(curl -s http://169.254.169.254/latest/meta-data/instance-id) --region ${AWS::Region} Tags: - Key: Name Value: PreProcessor CreationPolicy: ResourceSignal: Timeout: PT10M WebInstanceRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - ec2.amazonaws.com Action: - sts:AssumeRole Path: / Policies: - PolicyName: S3Assets PolicyDocument: Version: 2012-10-17 Statement: - Sid: GetServerCertificate Effect: Allow Action: - s3:Get* - s3:List* - s3:Head* Resource: - !Sub arn:${AWS::Partition}:s3:::${S3AssetsBucket}/* - !Sub arn:${AWS::Partition}:s3:::${S3AssetsBucket} - Sid: DescribeVolumes Effect: Allow Action: - ec2:DescribeVolumes Resource: '*' - PolicyName: aws-quick-start-s3-policy PolicyDocument: Version: '2012-10-17' Statement: - Action: - s3:GetObject Resource: !Sub - arn:${AWS::Partition}:s3:::${S3Bucket}/${QSS3KeyPrefix}* - S3Bucket: !If [UsingDefaultBucket, !Sub '${QSS3BucketName}-${AWS::Region}', !Ref QSS3BucketName] Effect: Allow ManagedPolicyArns: - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore WebInstanceProfile: Type: AWS::IAM::InstanceProfile Properties: Path: / Roles: - !Ref WebInstanceRole AutoScalingConfigWeb: Type: AWS::AutoScaling::LaunchConfiguration DependsOn: - ALBApp - AutoScalingGroupApp - PreProcInstance Metadata: AWS::CloudFormation::Init: config: packages: yum: #nginx: [] #java-1.6.0-openjdk-devel: [] git: [] files: /etc/nginx/snippets/self-signed.conf: content: | ssl_certificate /etc/ssl/certs/common.crt; ssl_certificate_key /etc/ssl/private/common.key; /tmp/nginx/default.conf: content: !Sub | server { listen 80 default_server; listen [::]:80 default_server; charset utf-8; location / { resolver xxxxx; return 301 https://$host$request_uri; } } server { listen 443 ssl http2 default_server; listen [::]:443 ssl http2 default_server; location / { resolver xxxxx; set $elb 'https://${ALBApp.DNSName}'; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_redirect off; proxy_pass $elb; } include snippets/self-signed.conf; } mode: '000755' owner: root group: root commands: 00-enable_ngnix: command: amazon-linux-extras install nginx1 01-enable_ngnix: command: amazon-linux-extras install java-openjdk11 02-nginx-setup: command: | #!/bin/bash ## Nginx setup sleep 5 echo 'Replace resolver placeholder with /etc/resolv.conf nameservers' sed -i "s/xxxxx/$(grep ^nameserver /etc/resolv.conf | sed 's/^nameserver//' | tr -d '\n')/" /tmp/nginx/default.conf cp /tmp/nginx/default.conf /etc/nginx/conf.d/default.conf service nginx stop sed -i '/default_server;/d' /etc/nginx/nginx.conf sleep 10 service nginx restart services: sysvinit: nginx: enabled: true ensureRunning: true files: - /etc/nginx/conf.d/default.conf Properties: AssociatePublicIpAddress: true ImageId: !Ref WebServerAMI IamInstanceProfile: !Ref WebInstanceProfile InstanceType: !Ref WebInstanceType BlockDeviceMappings: - DeviceName: /dev/sdh Ebs: VolumeSize: 50 VolumeType: gp2 Encrypted: true KeyName: !Ref EC2KeyPair SecurityGroups: - !Ref SecurityGroupWebInstance UserData: Fn::Base64: !Sub | #!/bin/bash yum update -y # install SSL cert common key yum -y install mod_ssl aws configure set default.s3.signature_version s3v4 aws s3 cp s3://${S3AssetsBucket}/ssl/common.crt /etc/ssl/certs/common.crt --region ${AWS::Region} aws s3 cp s3://${S3AssetsBucket}/ssl/common.key /etc/ssl/private/common.key --region ${AWS::Region} EC2_INSTANCE_ID=$(curl -s http://instance-data/latest/meta-data/instance-id) /opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource AutoScalingConfigWeb --region ${AWS::Region} ## Nginx setup sleep 5 cp /tmp/nginx/default.conf /etc/nginx/conf.d/default.conf service nginx stop sed -i '/default_server;/d' /etc/nginx/nginx.conf sleep 10 service nginx restart WebTargetGroup: Type: AWS::ElasticLoadBalancingV2::TargetGroup Properties: VpcId: !Ref ProductionVPC TargetType: instance Port: 443 Protocol: HTTPS Matcher: HttpCode: 200,302,403,301 HealthCheckPath: /landing/index.html AutoScalingGroupWeb: Type: AWS::AutoScaling::AutoScalingGroup DependsOn: AutoScalingConfigWeb Properties: AvailabilityZones: - !Ref AvailabilityZoneA - !Ref AvailabilityZoneB VPCZoneIdentifier: - !Ref DMZSubnetA - !Ref DMZSubnetB LaunchConfigurationName: !Ref AutoScalingConfigWeb MinSize: "2" MaxSize: "4" TargetGroupARNs: - !Ref WebTargetGroup HealthCheckType: ELB HealthCheckGracePeriod: 300 Tags: - Key: Name Value: Proxy Server PropagateAtLaunch: true - Key: Environment Value: !Ref Environment PropagateAtLaunch: true AutoScalingUpWeb: Type: AWS::AutoScaling::ScalingPolicy Properties: AdjustmentType: ChangeInCapacity AutoScalingGroupName: !Ref AutoScalingGroupWeb Cooldown: "500" ScalingAdjustment: 1 AutoScalingDownWeb: Type: AWS::AutoScaling::ScalingPolicy Properties: AdjustmentType: ChangeInCapacity AutoScalingGroupName: !Ref AutoScalingGroupWeb Cooldown: "500" ScalingAdjustment: -1 CWAlarmHighCPUWeb: Type: AWS::CloudWatch::Alarm Properties: EvaluationPeriods: 1 Statistic: Average Threshold: 50 AlarmDescription: Alarm if CPU too high or metric disappears indicating instance is down Period: 60 AlarmActions: - !Ref AutoScalingUpWeb Namespace: AWS/EC2 Dimensions: - Name: AutoScalingGroupName Value: !Ref AutoScalingGroupWeb ComparisonOperator: GreaterThanThreshold MetricName: CPUUtilization CWAlarmLowCPUWeb: Type: AWS::CloudWatch::Alarm DependsOn: AutoScalingGroupWeb Properties: EvaluationPeriods: 1 Statistic: Average Threshold: 10 AlarmDescription: Alarm if CPU too low, remove a web server Period: 60 AlarmActions: - !Ref AutoScalingDownWeb Namespace: AWS/EC2 Dimensions: - Name: AutoScalingGroupName Value: !Ref AutoScalingGroupWeb ComparisonOperator: LessThanThreshold MetricName: CPUUtilization AppInstanceRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - ec2.amazonaws.com Action: - sts:AssumeRole Path: / Policies: - PolicyName: S3Assets PolicyDocument: Version: 2012-10-17 Statement: - Sid: GetServerCertificate Effect: Allow Action: - s3:Get* - s3:List* - s3:Head* Resource: - !Sub arn:${AWS::Partition}:s3:::${S3AssetsBucket}/* - !Sub arn:${AWS::Partition}:s3:::${S3AssetsBucket} - !Sub - arn:${AWS::Partition}:s3:::${S3Bucket} - S3Bucket: !If [UsingDefaultBucket, !Sub '${QSS3BucketName}-${AWS::Region}', !Ref QSS3BucketName] - !Sub - arn:${AWS::Partition}:s3:::${S3Bucket}/${QSS3KeyPrefix}* - S3Bucket: !If [UsingDefaultBucket, !Sub '${QSS3BucketName}-${AWS::Region}', !Ref QSS3BucketName] - Sid: DescribeVolumes Effect: Allow Action: - ec2:DescribeVolumes Resource: '*' - Sid: getSecret Effect: Allow Action: - secretsmanager:GetSecretValue Resource: "*" - PolicyName: aws-quick-start-s3-policy PolicyDocument: Version: '2012-10-17' Statement: - Action: - s3:GetObject Resource: - !Sub - arn:${AWS::Partition}:s3:::${S3Bucket} - S3Bucket: !If [UsingDefaultBucket, !Sub '${QSS3BucketName}-${AWS::Region}', !Ref QSS3BucketName] - !Sub - arn:${AWS::Partition}:s3:::${S3Bucket}/${QSS3KeyPrefix}* - S3Bucket: !If [UsingDefaultBucket, !Sub '${QSS3BucketName}-${AWS::Region}', !Ref QSS3BucketName] Effect: Allow ManagedPolicyArns: - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore AppInstanceProfile: Type: AWS::IAM::InstanceProfile Properties: Path: / Roles: - !Ref AppInstanceRole AutoScalingConfigApp: Type: AWS::AutoScaling::LaunchConfiguration DependsOn: - PreProcInstance Metadata: AWS::CloudFormation::Init: configSets: wordpress_install: - install_cfn - install_wordpress install_cfn: files: /etc/cfn/cfn-hup.conf: content: !Sub | [main] stack=${AWS::StackId} region=${AWS::Region} mode: '000400' owner: root group: root /etc/cfn/hooks.d/cfn-auto-reloader.conf: content: !Sub | [cfn-auto-reloader-hook] triggers=post.update path=Resources.AutoScalingConfigApp.Metadata.AWS::CloudFormation::Init action=/opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource AutoScalingGroupApp --configsets wordpress_install --region ${AWS::Region} mode: '000400' owner: root group: root services: sysvinit: cfn-hup: enabled: true ensureRunning: true files: - /etc/cfn/cfn-hup.conf - /etc/cfn/hooks.d/cfn-auto-reloader.conf install_wordpress: packages: yum: jq: [] awslogs: [] httpd: [] #php: [] #php-mysql: [] php-mbstring.x86_64: [] sources: /var/www/html: 'https://wordpress.org/wordpress-5.1.1.tar.gz' /usr/share/php/aws-php: 'https://docs.aws.amazon.com/aws-sdk-php/v3/download/aws.zip' files: /tmp/httpd/ssl.conf: content: | Listen 443 DocumentRoot /var/www/html LoadModule ssl_module /etc/httpd/modules/mod_ssl.so SSLEngine on SSLCertificateFile /etc/ssl/certs/common.crt SSLCertificateKeyFile /etc/ssl/private/common.key mode: '000755' owner: root group: root /var/www/html/wordpress/wp-config.php: content: !Sub | '2017-10-17', 'region' => '${AWS::Region}', 'credentials' => $profile ]); $secretName = "${SecretKeyName}"; try { $result = $client->getSecretValue([ 'SecretId' => $secretName, ]); $secretObject = json_decode($result["SecretString"]); $password = $secretObject->{'password'}; } catch (AwsException $e) { $error = $e->getAwsErrorCode(); if ($error == 'DecryptionFailureException') { // Secrets Manager can't decrypt the protected secret text using the provided AWS KMS key. // Handle the exception here, and/or rethrow as needed. throw $e; } if ($error == 'InternalServiceErrorException') { // An error occurred on the server side. // Handle the exception here, and/or rethrow as needed. throw $e; } if ($error == 'InvalidParameterException') { // You provided an invalid value for a parameter. // Handle the exception here, and/or rethrow as needed. throw $e; } if ($error == 'InvalidRequestException') { // You provided a parameter value that is not valid for the current state of the resource. // Handle the exception here, and/or rethrow as needed. throw $e; } if ($error == 'ResourceNotFoundException') { // We can't find the resource that you asked for. // Handle the exception here, and/or rethrow as needed. throw $e; } } // ** MySQL settings - You can get this info from your web host ** // /** The name of the database for WordPress */ define( 'DB_NAME', '${DBName}' ); /** MySQL database username */ define( 'DB_USER', '${DBUser}' ); /** MySQL database password */ define( 'DB_PASSWORD', $password ); /** MySQL hostname */ define( 'DB_HOST', '${DBHost}' ); /** Database Charset to use in creating database tables. */ define( 'DB_CHARSET', 'utf8' ); /** The Database Collate type. Don't change this if in doubt. */ define( 'DB_COLLATE', '' ); /** * WordPress Database Table prefix. * * You can have multiple installations in one database if you give each * a unique prefix. Only numbers, letters, and underscores please! */ $table_prefix = 'wp_'; /** * For developers: WordPress debugging mode. * * Change this to true to enable the display of notices during development. * It is strongly recommended that plugin and theme developers use WP_DEBUG * in their development environments. * * For information on other constants that can be used for debugging, * visit the Codex. * * @link https://codex.wordpress.org/Debugging_in_WordPress */ define('WP_DEBUG', false ); define('WP_ALLOW_REPAIR', true); /* That's all, stop editing! Happy publishing. */ /** Absolute path to the WordPress directory. */ if ( ! defined( 'ABSPATH' ) ) { define( 'ABSPATH', dirname( __FILE__ ) . '/' ); } /** Sets up WordPress vars and included files. */ require_once( ABSPATH . 'wp-settings.php' ); mode: '000644' owner: root group: root services: sysvinit: httpd: enabled: true ensureRunning: true Properties: ImageId: !Ref AppAmi IamInstanceProfile: !Ref AppInstanceProfile InstanceType: !Ref AppInstanceType BlockDeviceMappings: - DeviceName: /dev/sdh Ebs: VolumeSize: 50 VolumeType: gp2 Encrypted: true KeyName: !Ref EC2KeyPair SecurityGroups: - !Ref SecurityGroupAppInstance UserData: Fn::Base64: !Sub - | #!/bin/bash -x yum update -y amazon-linux-extras install -y lamp-mariadb10.2-php7.2 php7.2 yum -y install php-mbstring.x86_64 mod_ssl aws configure set default.s3.signature_version s3v4 aws s3 cp s3://${S3AssetsBucket}/ssl/common.crt /etc/ssl/certs/common.crt --region ${AWS::Region} aws s3 cp s3://${S3AssetsBucket}/ssl/common.key /etc/ssl/private/common.key --region ${AWS::Region} EC2_INSTANCE_ID=$(curl -s http://instance-data/latest/meta-data/instance-id) /opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource AutoScalingConfigApp --configsets wordpress_install --region ${AWS::Region} cp /tmp/httpd/ssl.conf /etc/httpd/conf.d/ssl.conf service httpd restart ###################################################################### # NOTE: UPDATE THESE VALUES ACCORDING TO THE COMPLIANCE BODY # ###################################################################### LANDING_PAGE="/var/www/html/landing.html" ###################################################################### # Download the landing page. sudo aws s3 cp --region=${S3Region} s3://${S3Bucket}/${QSS3KeyPrefix}landing /var/www/html/landing --recursive /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource AutoScalingGroupApp --region ${AWS::Region} - S3Region: !If [UsingDefaultBucket, !Ref 'AWS::Region', !Ref QSS3BucketRegion] S3Bucket: !If [UsingDefaultBucket, !Sub '${QSS3BucketName}-${AWS::Region}', !Ref QSS3BucketName] AppTargetGroup: Type: AWS::ElasticLoadBalancingV2::TargetGroup Properties: VpcId: !Ref ProductionVPC TargetType: instance Port: 443 Protocol: HTTPS Matcher: HttpCode: 200,302,403,301 HealthCheckPath: /landing/index.html AutoScalingGroupApp: Type: AWS::AutoScaling::AutoScalingGroup DependsOn: AutoScalingConfigApp Properties: AvailabilityZones: - !Ref AvailabilityZoneA - !Ref AvailabilityZoneB VPCZoneIdentifier: - !Ref AppPrivateSubnetA - !Ref AppPrivateSubnetB LaunchConfigurationName: !Ref AutoScalingConfigApp MinSize: "2" MaxSize: "4" TargetGroupARNs: - !Ref AppTargetGroup HealthCheckType: ELB HealthCheckGracePeriod: 300 Tags: - Key: Name Value: AppServer PropagateAtLaunch: true - Key: Environment Value: !Ref Environment PropagateAtLaunch: true CreationPolicy: ResourceSignal: Count: 1 Timeout: PT15M AutoScalingDownApp: Type: AWS::AutoScaling::ScalingPolicy Properties: AdjustmentType: ChangeInCapacity AutoScalingGroupName: !Ref AutoScalingGroupApp Cooldown: "1" ScalingAdjustment: 1 AutoScalingUpApp: Type: AWS::AutoScaling::ScalingPolicy Properties: AdjustmentType: ChangeInCapacity AutoScalingGroupName: !Ref AutoScalingGroupApp Cooldown: "1" ScalingAdjustment: -1 CWAlarmHighCPUApp: Type: AWS::CloudWatch::Alarm Properties: EvaluationPeriods: 1 Statistic: Average Threshold: 50 AlarmDescription: Alarm if CPU too high or metric disappears indicating instance is down Period: 60 AlarmActions: - !Ref AutoScalingDownApp Namespace: AWS/EC2 Dimensions: - Name: AutoScalingGroupName Value: !Ref AutoScalingGroupApp ComparisonOperator: GreaterThanThreshold MetricName: CPUUtilization CWAlarmLowCPUApp: Type: AWS::CloudWatch::Alarm Properties: EvaluationPeriods: 1 Statistic: Average Threshold: 10 AlarmDescription: Alarm if CPU too low, remove an app server Period: 60 AlarmActions: - !Ref AutoScalingUpApp Namespace: AWS/EC2 Dimensions: - Name: AutoScalingGroupName Value: !Ref AutoScalingGroupApp ComparisonOperator: LessThanThreshold MetricName: CPUUtilization PostProcInstanceRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - ec2.amazonaws.com Action: - sts:AssumeRole Path: / Policies: - PolicyName: PostProcPermissions PolicyDocument: Version: 2012-10-17 Statement: - Sid: UploadServerCertificate Effect: Allow Action: - iam:ListServerCertificates - iam:UploadServerCertificate - iam:CreateServiceLinkedRole - s3:PutObject - s3:GetObject - s3:DeleteObject Resource: - '*' - Sid: ELBListnerAndWAF Effect: Allow Action: - elasticloadbalancing:CreateLoadBalancerListeners - elasticloadbalancing:DescribeTargetGroups - elasticloadbalancing:CreateListener #- waf-regional:ListWebACLs Resource: - '*' - Sid: SelfDestruct Effect: Allow Action: - ec2:TerminateInstances Resource: - '*' PostProcInstanceProfile: Type: AWS::IAM::InstanceProfile Properties: Path: / Roles: - !Ref PostProcInstanceRole PostProcInstance4: Type: AWS::EC2::Instance DependsOn: - AutoScalingGroupApp - AutoScalingGroupWeb Properties: ImageId: !Ref WebServerAMI #InstanceType: !Ref AppInstanceType InstanceType: t3.large IamInstanceProfile: !Ref PostProcInstanceProfile SubnetId: !Ref AppPrivateSubnetA SecurityGroupIds: - !Ref SecurityGroupAppInstance UserData: Fn::Base64: !Sub | #!/bin/bash -xe echo Force S3 to use Sigv4 aws configure set default.s3.signature_version s3v4 echo Configure the region, necessary especially for GovCloud aws configure set region ${AWS::Region} yum -y install php-mbstring.x86_64 mod_ssl aws configure set default.s3.signature_version s3v4 aws s3 cp s3://${S3AssetsBucket}/ssl/common.crt /etc/ssl/certs/common.crt --region ${AWS::Region} aws s3 cp s3://${S3AssetsBucket}/ssl/common.key /etc/ssl/private/common.key --region ${AWS::Region} echo Determine whether a certificate needs to be generated cert_arn=$(aws iam list-server-certificates --query 'ServerCertificateMetadataList[?ServerCertificateName==`non-production-testing-server-cert`].Arn' --output text) if [[ $(echo "$cert_arn" | grep "non-production-testing-server-cert") != *"non-production-testing-server-cert"* ]]; then cert_arn=$(aws iam upload-server-certificate --server-certificate-name non-production-testing-server-cert --query 'ServerCertificateMetadata.Arn' --output text --certificate-body file:///etc/ssl/certs/common.crt --private-key file:///etc/ssl/private/common.key) echo Sleeping so IAM can propogate the certificate... sleep 10 echo Removing key files... #rm /tmp/*.pem fi aws s3 rm s3://${S3AssetsBucket}/ssl/common.crt aws s3 rm s3://${S3AssetsBucket}/ssl/common.key echo Creating ALB HTTPS listener using the cert stored in the environment variable... aws elbv2 create-listener --load-balancer-arn ${ALBWeb} --protocol HTTPS --port 443 --certificates CertificateArn=$cert_arn --default-actions Type=forward,TargetGroupArn=${WebTargetGroup} --region ${AWS::Region} aws elbv2 create-listener --load-balancer-arn ${ALBApp} --protocol HTTPS --port 443 --certificates CertificateArn=$cert_arn --default-actions Type=forward,TargetGroupArn=${AppTargetGroup} --region ${AWS::Region} echo Sleeping for 1.5 minutes to allow CloudFormation to catch up sleep 90 #echo Signal for success echo Self-destruct! aws ec2 terminate-instances --instance-id $(curl -s http://169.254.169.254/latest/meta-data/instance-id) --region ${AWS::Region} Tags: - Key: Name Value: PostProcessor rALBWebACL: Type: AWS::WAFv2::WebACL Properties: Name: ALBWebACL Scope: REGIONAL Description: This is a Sample WebACL DefaultAction: Allow: {} VisibilityConfig: SampledRequestsEnabled: true CloudWatchMetricsEnabled: true MetricName: KISMSWebACLMetric Rules: - Name: AWS-AWSManagedRulesAmazonIpReputationList Priority: 0 OverrideAction: !If - ActionCondition - None: {} - Count: {} VisibilityConfig: SampledRequestsEnabled: true CloudWatchMetricsEnabled: true MetricName: MetricForAMRAIRL Statement: ManagedRuleGroupStatement: VendorName: AWS Name: AWSManagedRulesAmazonIpReputationList ExcludedRules: [] - Name: AWS-AWSManagedRulesAdminProtectionRuleSet Priority: 1 OverrideAction: !If - ActionCondition - None: {} - Count: {} VisibilityConfig: SampledRequestsEnabled: true CloudWatchMetricsEnabled: true MetricName: MetricForAMRAPRS Statement: ManagedRuleGroupStatement: VendorName: AWS Name: AWSManagedRulesAdminProtectionRuleSet ExcludedRules: [] - Name: AWS-AWSManagedRulesCommonRuleSet Priority: 2 OverrideAction: !If - ActionCondition - None: {} - Count: {} VisibilityConfig: SampledRequestsEnabled: true CloudWatchMetricsEnabled: true MetricName: MetricForAMRCRS Statement: ManagedRuleGroupStatement: VendorName: AWS Name: AWSManagedRulesCommonRuleSet ExcludedRules: [] - Name: AWS-AWSManagedRulesKnownBadInputsRuleSet Priority: 3 OverrideAction: !If - ActionCondition - None: {} - Count: {} VisibilityConfig: SampledRequestsEnabled: true CloudWatchMetricsEnabled: true MetricName: MetricForAMRKBIRS Statement: ManagedRuleGroupStatement: VendorName: AWS Name: AWSManagedRulesKnownBadInputsRuleSet ExcludedRules: [] - Name: AWS-AWSManagedRulesSQLiRuleSet Priority: 4 OverrideAction: !If - ActionCondition - None: {} - Count: {} VisibilityConfig: SampledRequestsEnabled: true CloudWatchMetricsEnabled: true MetricName: MetricForAMRSRS Statement: ManagedRuleGroupStatement: VendorName: AWS Name: AWSManagedRulesSQLiRuleSet ExcludedRules: [] rALBACLAssociation: Type: 'AWS::WAFv2::WebACLAssociation' Properties: WebACLArn: !GetAtt - rALBWebACL - Arn ResourceArn: !Ref ALBWeb Outputs: LandingPageURL: Value: !Sub https://${ALBWeb.DNSName}/landing/index.html Description: Landing Page WebsiteURL: Value: !Sub https://${ALBWeb.DNSName}/wordpress/wp-admin/install.php Description: WordPress Website (demonstration purposes only) SecurityGroupWeb: Value: !Ref SecurityGroupWeb SecurityGroupApp: Value: !Ref SecurityGroupApp SecurityGroupRDS: Value: !Ref SecurityGroupRDS SurveyLink: Value: https://aws.au1.qualtrics.com/SE/?SID=SV_55sYYdtY1NhTTgN&qs=hipaa Description: Please take a moment to complete the survey by clicking this link Help: Description: For assistance or questions regarding this quickstart please email compliance-accelerator@amazon.com Value: '' ...