---
AWSTemplateFormatVersion: 2010-09-09
Description: Provides nesting for required stacks to deploy a full sample web application
  with reverse proxy, ELBs, IAM, and other resources (for demonstration/POC/testing)
  QS(0029)
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: Region Config
      Parameters:
      - pRegionAZ1Name
      - pRegionAZ2Name
    - Label:
        default: Network (existing VPC config)
      Parameters:
      - pProductionCIDR
      - pProductionVPC
      - pDMZSubnetA
      - pDMZSubnetB
      - pAppPrivateSubnetA
      - pAppPrivateSubnetB
      - pDBPrivateSubnetA
      - pDBPrivateSubnetB
    - Label:
        default: Application Configuration
      Parameters:
      - pWebInstanceType
      - pWebServerAMI
      - pAppInstanceType
      - pAppAmi
    - Label:
        default: Database Configuration
      Parameters:
      - pDBName
      - pDBUser
      - pDBPassword
      - pDBClass
      - pDBAllocatedStorage
    - Label:
        default: AWS Quick Start Configuration
      Parameters:
      - QSS3BucketName
      - QSS3KeyPrefix
    - Label:
        default: Wazuh agents
      Parameters:
      - WazuhManagerIp
Parameters:
  pSecurityAlarmTopic:
    Description: SNS topic for alarms and notifications
    Type: String
    Default: ''
  pEC2KeyPair:
    Description: Key Name for Instance
    Type: String
    Default: ''
  pProductionCIDR:
    Description: Production VPC CIDR
    Type: String
  pManagementCIDR:
    Description: Management VPC CIDR
    Type: String
  pProductionVPC:
    Description: Production VPC
    Type: AWS::EC2::VPC::Id
  pBastionSSHCIDR:
    Description: CIDR block (optional) of Public IPs allowed to access Bastion instance
      in this deployment
    Type: String
    Default: 0.0.0.0/0
  pDMZSubnetA:
    Description: DMZ Subnet A
    Type: AWS::EC2::Subnet::Id
  pDMZSubnetB:
    Description: DMZ Subnet B
    Type: AWS::EC2::Subnet::Id
  pAppPrivateSubnetA:
    Description: WebApp Subnet A
    Type: AWS::EC2::Subnet::Id
  pAppPrivateSubnetB:
    Description: WebApp Subnet A
    Type: AWS::EC2::Subnet::Id
  pWebInstanceType:
    Description: Instance type for the webservers
    Type: String
  pAppInstanceType:
    Description: Instance type for the app webservers
    Type: String
  pDBPrivateSubnetA:
    Description: rDBPrivateSubnetA
    Type: AWS::EC2::Subnet::Id
  pDBPrivateSubnetB:
    Description: rDBPrivateSubnetB
    Type: AWS::EC2::Subnet::Id
  pRegionAZ1Name:
    Description: rDBPrivateSubnetB
    Type: AWS::EC2::AvailabilityZone::Name
  pRegionAZ2Name:
    Description: rDBPrivateSubnetB
    Type: AWS::EC2::AvailabilityZone::Name
  pWebServerAMI:
    Description: Which webserver AMI do you want to use, default
    Type: String
    Default: none
  pAppAmi:
    Description: Which App AMI do you want to use?
    Type: String
    Default: none
  pDBName:
    Description: Name of RDS Database
    Type: String
  pDBUser:
    Description: Username of DB Instance
    Type: String
  pDBPassword:
    Description: Password of DB Instance
    NoEcho: true
    Type: String
  pDBClass:
    Description: Instance class of RDS instance
    Type: String
  pDBAllocatedStorage:
    Description: Allocated Storage (in GB) for RDS instance
    Type: String
  pEnvironment:
    Description: Environment type (development, test, or production)
    Type: String
    Default: development
  pSupportsGlacier:
    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
  QSS3KeyPrefix:
    AllowedPattern: ^[0-9a-zA-Z-/]*$
    ConstraintDescription: Quick Start key prefix can include numbers, lowercase letters,
      uppercase letters, hyphens (-), and forward slash (/).
    Default: quickstart-compliance-nist-high/
    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
  WazuhManagerIp:
    Description: URL of the Wazuh cluster load balancer
    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:
  GovCloudCondition:
    !Equals
    - !Ref AWS::Region
    - us-gov-west-1
  SupportsGlacier:
    !Equals
    - !Ref pSupportsGlacier
    - 'true'
Resources:
  rS3ELBAccessLogs:
    Type: AWS::S3::Bucket
    DeletionPolicy: Retain
    Properties:
      AccessControl: Private
  rS3AccessLogsPolicy:
    Type: AWS::S3::BucketPolicy
    DeletionPolicy: Retain
    Properties:
      Bucket: !Ref rS3ELBAccessLogs
      PolicyDocument:
        Version: 2008-10-17
        Statement:
        - Sid: ELBAccessLogs20130930
          Effect: Allow
          Resource:
            !Sub
            - arn:${Partition}:s3:::${rS3ELBAccessLogs}/Logs/AWSLogs/${AWS::AccountId}/*
            - Partition:
                !If
                - GovCloudCondition
                - aws-us-gov
                - aws
          Principal:
            AWS:
              !FindInMap
              - elbMap
              - !Ref AWS::Region
              - ELB
          Action:
          - s3:PutObject
  rSecurityGroupWeb:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Security group for Reverse Proxy in DMZ
      VpcId: !Ref pProductionVPC
      SecurityGroupIngress:
      - IpProtocol: tcp
        FromPort: 443
        ToPort: 443
        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 pEnvironment
  rSecurityGroupWebInstance:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Security group for Reverse Proxy Instances in DMZ
      VpcId: !Ref pProductionVPC
      SecurityGroupIngress:
      - IpProtocol: tcp
        FromPort: 443
        ToPort: 443
        CidrIp: !Ref pProductionCIDR
      - IpProtocol: tcp
        FromPort: 80
        ToPort: 80
        CidrIp: !Ref pProductionCIDR
      - IpProtocol: tcp
        FromPort: 22
        ToPort: 22
        CidrIp: !Ref pManagementCIDR
      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: tcp
        FromPort: 4120
        ToPort: 4120
        CidrIp: 0.0.0.0/0
      - IpProtocol: udp
        FromPort: 123
        ToPort: 123
        CidrIp: !Ref pProductionCIDR
      Tags:
      - Key: Name
        Value: sg-reverse-proxy-dmz-instances
      - Key: Environment
        Value: !Ref pEnvironment
  rSecurityGroupApp:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Security group for Appservers ELB
      VpcId: !Ref pProductionVPC
      SecurityGroupIngress:
      - IpProtocol: tcp
        FromPort: 443
        ToPort: 443
        CidrIp: !Ref pProductionCIDR
      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 pEnvironment
  rSecurityGroupAppInstance:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Security group for Appserver Instances
      VpcId: !Ref pProductionVPC
      SecurityGroupIngress:
      - IpProtocol: tcp
        FromPort: 443
        ToPort: 443
        CidrIp: !Ref pProductionCIDR
      - IpProtocol: tcp
        FromPort: 80
        ToPort: 80
        CidrIp: !Ref pProductionCIDR
      - IpProtocol: tcp
        FromPort: 22
        ToPort: 22
        CidrIp: !Ref pManagementCIDR
      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: tcp
        FromPort: 4120
        ToPort: 4120
        CidrIp: 0.0.0.0/0
      - IpProtocol: udp
        FromPort: 123
        ToPort: 123
        CidrIp: !Ref pProductionCIDR
      - IpProtocol: tcp
        FromPort: 3306
        ToPort: 3306
        CidrIp: !Ref pProductionCIDR
      Tags:
      - Key: Name
        Value: sg-app-server-elb-instances
      - Key: Environment
        Value: !Ref pEnvironment
  rSecurityGroupRDS:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Port 3306 database for access
      VpcId: !Ref pProductionVPC
      SecurityGroupIngress:
      - IpProtocol: tcp
        FromPort: 3306
        ToPort: 3306
        SourceSecurityGroupId: !Ref rSecurityGroupAppInstance
      Tags:
      - Key: Name
        Value: sg-database-access
      - Key: Environment
        Value: !Ref pEnvironment
  rWebContentBucket:
    Type: AWS::S3::Bucket
    Properties:
      AccessControl: Private
      LifecycleConfiguration:
        Rules:
        - Id: Transition90daysRetain7yrs
          Status: Enabled
          ExpirationInDays: 2555
          Transition:
            TransitionInDays: 90
            StorageClass:
              !If
              - SupportsGlacier
              - GLACIER
              - STANDARD_IA
      VersioningConfiguration:
        Status: Enabled
    DeletionPolicy: Delete
  rWebContentS3Policy:
    Type: AWS::S3::BucketPolicy
    DependsOn: rWebContentBucket
    Properties:
      Bucket: !Ref rWebContentBucket
      PolicyDocument:
        Statement:
        - Sid: EnforceSecureTransport
          Action: s3:*
          Effect: Deny
          Principal: '*'
          Resource:
            !Sub
            - arn:${Partition}:s3:::${rWebContentBucket}
            - Partition:
                !If
                - GovCloudCondition
                - aws-us-gov
                - aws
          Condition:
            Bool:
              aws:SecureTransport: 'false'
        - Sid: EnforceEncryptionOnPut
          Effect: Deny
          Principal: '*'
          Action: s3:PutObject
          Resource:
            !Sub
            - arn:${Partition}:s3:::${rWebContentBucket}/*
            - Partition:
                !If
                - GovCloudCondition
                - aws-us-gov
                - aws
          Condition:
            StringNotEquals:
              s3:x-amz-server-side-encryption: AES256
  rDBSubnetGroup:
    Type: AWS::RDS::DBSubnetGroup
    Properties:
      DBSubnetGroupDescription: MySQL RDS Subnet Group
      SubnetIds:
      - !Ref pDBPrivateSubnetA
      - !Ref pDBPrivateSubnetB
  rRDSInstanceMySQL:
    Type: AWS::RDS::DBInstance
    DependsOn:
    - rDBSubnetGroup
    - rSecurityGroupRDS
    Properties:
      DBName: !Ref pDBName
      Engine: MySQL
      MultiAZ: true
      StorageEncrypted: true
      MasterUsername: !Ref pDBUser
      MasterUserPassword: !Ref pDBPassword
      DBInstanceClass: !Ref pDBClass
      AllocatedStorage: !Ref pDBAllocatedStorage
      VPCSecurityGroups:
      - !Ref rSecurityGroupRDS
      DBSubnetGroupName: !Ref rDBSubnetGroup
  rELBApp:
    Type: AWS::ElasticLoadBalancing::LoadBalancer
    DependsOn:
    - rS3ELBAccessLogs
    - rSecurityGroupApp
    - rS3AccessLogsPolicy
    Properties:
      Subnets:
      - !Ref pAppPrivateSubnetA
      - !Ref pAppPrivateSubnetB
      HealthCheck:
        HealthyThreshold: "2"
        Interval: "15"
        Target: TCP:80
        Timeout: "5"
        UnhealthyThreshold: "3"
      AccessLoggingPolicy:
        S3BucketName: !Ref rS3ELBAccessLogs
        S3BucketPrefix: Logs
        Enabled: true
        EmitInterval: 60
      SecurityGroups:
      - !Ref rSecurityGroupApp
      Listeners:
      - InstancePort: "80"
        LoadBalancerPort: "80"
        Protocol: HTTP
        InstanceProtocol: HTTP
      Scheme: internal
      Tags:
      - Key: Name
        Value: ProxyELB
      - Key: Environment
        Value: !Ref pEnvironment
  rELBWeb:
    Type: AWS::ElasticLoadBalancing::LoadBalancer
    DependsOn:
    - rS3ELBAccessLogs
    - rSecurityGroupWeb
    - rS3AccessLogsPolicy
    Properties:
      Subnets:
      - !Ref pDMZSubnetA
      - !Ref pDMZSubnetB
      HealthCheck:
        HealthyThreshold: "2"
        Interval: "30"
        Target: TCP:80
        Timeout: "5"
        UnhealthyThreshold: "5"
      AccessLoggingPolicy:
        S3BucketName: !Ref rS3ELBAccessLogs
        S3BucketPrefix: Logs
        Enabled: true
        EmitInterval: 60
      SecurityGroups:
      - !Ref rSecurityGroupWeb
      Listeners:
      - InstancePort: "80"
        LoadBalancerPort: "80"
        Protocol: HTTP
        InstanceProtocol: HTTP
      Tags:
      - Key: Name
        Value: Proxy ELB
      - Key: Environment
        Value: !Ref pEnvironment
  rWebInstanceRole:
    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: DescribeVolumes
            Effect: Allow
            Action:
            - ec2:DescribeVolumes
            Resource: '*'
  rWebInstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Path: /
      Roles:
      - !Ref rWebInstanceRole
  rAutoScalingConfigWeb:
    Type: AWS::AutoScaling::LaunchConfiguration
    DependsOn:
    - rELBApp
    - rAutoScalingGroupApp
    Metadata:
      AWS::CloudFormation::Init:
        config:
          packages:
            yum:
              nginx: []
              java-1.6.0-openjdk-devel: []
              git: []
          files:
            /tmp/nginx/default.conf:
              content: !Sub |
                server {
                  listen 80;
                  charset utf-8;
                  location / {
                    resolver xxxxx;
                    set $elb 'https://${rELBApp.DNSName}';
                    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                    proxy_set_header Host $http_host;
                    proxy_redirect off;
                    proxy_pass $elb;
                  }
                }
              mode: '000755'
              owner: root
              group: root
          commands:
            01-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 pWebServerAMI
      IamInstanceProfile: !Ref rWebInstanceProfile
      InstanceType: !Ref pWebInstanceType
      BlockDeviceMappings:
      - DeviceName: /dev/sdh
        Ebs:
          VolumeSize: 50
          VolumeType: gp2
          Encrypted: true
      KeyName: !Ref pEC2KeyPair
      SecurityGroups:
      - !Ref rSecurityGroupWebInstance
      UserData:
        Fn::Base64: !Sub
        - |
          #!/bin/bash
          yum update -y

          EC2_INSTANCE_ID=$(curl -s http://instance-data/latest/meta-data/instance-id)

          ######################################################################
          # Volume /dev/sdh (which will get created as /dev/xvdh on Amazon Linux)

          DATA_STATE="unknown"
          until [ "${!DATA_STATE}" == "attached" ]; do
            DATA_STATE=$(aws ec2 describe-volumes \
            --region ${AWS::Region} \
            --filters \
                  Name=attachment.instance-id,Values=${!EC2_INSTANCE_ID} \
                  Name=attachment.device,Values=/dev/sdh \
            --query Volumes[].Attachments[].State \
            --output text)

            sleep 5
          done

          # Format /dev/xvdh if it does not contain a partition yet
          if [ "$(file -b -s /dev/xvdh)" == "data" ]; then
            mkfs -t ext4 /dev/xvdh
          fi

          mkdir -p /data
          mount /dev/xvdh /data

          # Persist the volume in /etc/fstab so it gets mounted again
          echo '/dev/xvdh /data ext4 defaults,nofail 0 2' >> /etc/fstab

          /opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource rAutoScalingConfigWeb --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

          # Install Wazuh agent
          cat > /etc/yum.repos.d/wazuh.repo <<\EOF
          [wazuh_repo]
          gpgcheck=1
          gpgkey=https://packages.wazuh.com/key/GPG-KEY-WAZUH
          enabled=1
          name=Wazuh repository
          baseurl=https://packages.wazuh.com/3.x/yum/
          protect=1
          EOF

          WAZUH_MANAGER_IP="${MasterIp}" yum install wazuh-agent
        - MasterIp: !Ref WazuhManagerIp
  rAutoScalingGroupWeb:
    Type: AWS::AutoScaling::AutoScalingGroup
    DependsOn: rAutoScalingConfigWeb
    Properties:
      AvailabilityZones:
      - !Ref pRegionAZ1Name
      - !Ref pRegionAZ2Name
      VPCZoneIdentifier:
      - !Ref pDMZSubnetA
      - !Ref pDMZSubnetB
      LaunchConfigurationName: !Ref rAutoScalingConfigWeb
      MinSize: "2"
      MaxSize: "4"
      LoadBalancerNames:
      - !Ref rELBWeb
      HealthCheckType: ELB
      HealthCheckGracePeriod: 300
      Tags:
      - Key: Name
        Value: Proxy Server
        PropagateAtLaunch: true
      - Key: Environment
        Value: !Ref pEnvironment
        PropagateAtLaunch: true
  rAutoScalingUpWeb:
    Type: AWS::AutoScaling::ScalingPolicy
    Properties:
      AdjustmentType: ChangeInCapacity
      AutoScalingGroupName: !Ref rAutoScalingGroupWeb
      Cooldown: "500"
      ScalingAdjustment: 1
  rAutoScalingDownWeb:
    Type: AWS::AutoScaling::ScalingPolicy
    Properties:
      AdjustmentType: ChangeInCapacity
      AutoScalingGroupName: !Ref rAutoScalingGroupWeb
      Cooldown: "500"
      ScalingAdjustment: -1
  rCWAlarmHighCPUWeb:
    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 rAutoScalingUpWeb
      Namespace: AWS/EC2
      Dimensions:
      - Name: AutoScalingGroupName
        Value: !Ref rAutoScalingGroupWeb
      ComparisonOperator: GreaterThanThreshold
      MetricName: WebServerCpuHighUtilization
  rCWAlarmLowCPUWeb:
    Type: AWS::CloudWatch::Alarm
    DependsOn: rAutoScalingGroupWeb
    Properties:
      EvaluationPeriods: 1
      Statistic: Average
      Threshold: 10
      AlarmDescription: Alarm if CPU too low, remove a web server
      Period: 60
      AlarmActions:
      - !Ref rAutoScalingDownWeb
      Namespace: AWS/EC2
      Dimensions:
      - Name: AutoScalingGroupName
        Value: !Ref rAutoScalingGroupWeb
      ComparisonOperator: LessThanThreshold
      MetricName: WebServerCpuLowUtilization
  rAppInstanceRole:
    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: DescribeVolumes
            Effect: Allow
            Action:
            - ec2:DescribeVolumes
            Resource: '*'
  rAppInstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Path: /
      Roles:
      - !Ref rAppInstanceRole
  rAutoScalingConfigApp:
    Type: AWS::AutoScaling::LaunchConfiguration
    DependsOn:
    - rRDSInstanceMySQL
    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.rAutoScalingConfigApp.Metadata.AWS::CloudFormation::Init
                action=/opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource rAutoScalingGroupApp --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:
              php: []
              php-mysql: []
              mysql: []
              httpd: []
          sources:
            /var/www/html: https://wordpress.org/latest.tar.gz
          files:
            /var/www/html/wordpress/wp-config.php:
              content: !Sub |
                <?php
                define('DB_NAME', '${pDBName}');
                define('DB_USER', '${pDBUser}');
                define('DB_PASSWORD', '${pDBPassword}');
                define('DB_HOST', '${rRDSInstanceMySQL.Endpoint.Address}');
                define('FORCE_SSL_ADMIN', true);
                if (strpos($_SERVER['HTTP_X_FORWARDED_PROTO'], 'https') !== false) { $_SERVER['HTTPS']='on'; }
                define('DB_CHARSET', 'utf8');
                define('DB_COLLATE', '');

                $table_prefix = 'wp_';
                define('WP_DEBUG', false);

                if ( !defined('ABSPATH') )
                  define('ABSPATH', dirname(__FILE__) . '/');

                require_once(ABSPATH . 'wp-settings.php');
              mode: '000644'
              owner: root
              group: root
          services:
            sysvinit:
              httpd:
                enabled: true
                ensureRunning: true
    Properties:
      ImageId: !Ref pAppAmi
      IamInstanceProfile: !Ref rAppInstanceProfile
      InstanceType: !Ref pAppInstanceType
      BlockDeviceMappings:
      - DeviceName: /dev/sdh
        Ebs:
          VolumeSize: 50
          VolumeType: gp2
          Encrypted: true
      KeyName: !Ref pEC2KeyPair
      SecurityGroups:
      - !Ref rSecurityGroupAppInstance
      UserData:
        Fn::Base64:
          !Sub
          - |
            #!/bin/bash -x

            yum update -y

            EC2_INSTANCE_ID=$(curl -s http://instance-data/latest/meta-data/instance-id)

            ######################################################################
            # Volume /dev/sdh (which will get created as /dev/xvdh on Amazon Linux)

            DATA_STATE="unknown"
            until [ "${!DATA_STATE}" == "attached" ]; do
              DATA_STATE=$(aws ec2 describe-volumes \
              --region ${AWS::Region} \
              --filters \
                    Name=attachment.instance-id,Values=${!EC2_INSTANCE_ID} \
                    Name=attachment.device,Values=/dev/sdh \
              --query Volumes[].Attachments[].State \
              --output text)

              sleep 5
            done

            # Format /dev/xvdh if it does not contain a partition yet
            if [ "$(file -b -s /dev/xvdh)" == "data" ]; then
              mkfs -t ext4 /dev/xvdh
            fi

            mkdir -p /data
            mount /dev/xvdh /data

            # Persist the volume in /etc/fstab so it gets mounted again
            echo '/dev/xvdh /data ext4 defaults,nofail 0 2' >> /etc/fstab

            /opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource rAutoScalingConfigApp --configsets wordpress_install --region ${AWS::Region}

            ######################################################################
            #     NOTE: UPDATE THESE VALUES ACCORDING TO THE COMPLIANCE BODY     #
            ######################################################################
            LANDING_PAGE="/var/www/html/landing.html"
            COMPLIANCE_BODY_LABEL="NIST 800-53"
            COMPLIANCE_SURVEY_LINK="nist"
            COMPLIANCE_MATRIX_FILENAME="NIST-800-53-Security-Controls-Mapping.xlsx"
            ######################################################################

            # Download the landing page.
            sudo wget https://${QSS3BucketName}.${QSS3Region}.amazonaws.com/${QSS3KeyPrefix}assets/landing/landing.html -O $LANDING_PAGE

            # Replace relative image links with links to the production S3 source.
            sudo sed -i 's|images|https://${QSS3BucketName}.${QSS3Region}.amazonaws.com/${QSS3KeyPrefix}assets/landing/images|g' $LANDING_PAGE

            # Inject the landing page branding label.
            sudo sed -i "s|{compliance-body}|$COMPLIANCE_BODY_LABEL|g" $LANDING_PAGE

            # Inject the survey link parameter.
            sudo sed -i "s|{compliance-body-survey-link}|$COMPLIANCE_SURVEY_LINK|g" $LANDING_PAGE

            # Inject the security control matrix file location.
            sudo sed -i "s|{compliance-body-matrix}|https://${QSS3BucketName}.${QSS3Region}.amazonaws.com/${QSS3KeyPrefix}assets/$COMPLIANCE_MATRIX_FILENAME|g" $LANDING_PAGE

            # Install Wazuh agent
            cat > /etc/yum.repos.d/wazuh.repo <<\EOF
            [wazuh_repo]
            gpgcheck=1
            gpgkey=https://packages.wazuh.com/key/GPG-KEY-WAZUH
            enabled=1
            name=Wazuh repository
            baseurl=https://packages.wazuh.com/3.x/yum/
            protect=1
            EOF

            WAZUH_MANAGER_IP="${MasterIp}" yum install wazuh-agent -y

          - QSS3Region:
              !If
              - GovCloudCondition
              - s3-us-gov-west-1
              - s3
            MasterIp: !Ref WazuhManagerIp
  rAutoScalingGroupApp:
    Type: AWS::AutoScaling::AutoScalingGroup
    DependsOn: rAutoScalingConfigApp
    Properties:
      AvailabilityZones:
      - !Ref pRegionAZ1Name
      - !Ref pRegionAZ2Name
      VPCZoneIdentifier:
      - !Ref pAppPrivateSubnetA
      - !Ref pAppPrivateSubnetB
      LaunchConfigurationName: !Ref rAutoScalingConfigApp
      MinSize: "2"
      MaxSize: "4"
      LoadBalancerNames:
      - !Ref rELBApp
      HealthCheckType: ELB
      HealthCheckGracePeriod: 300
      Tags:
      - Key: Name
        Value: AppServer
        PropagateAtLaunch: true
      - Key: Environment
        Value: !Ref pEnvironment
        PropagateAtLaunch: true
  rAutoScalingDownApp:
    Type: AWS::AutoScaling::ScalingPolicy
    Properties:
      AdjustmentType: ChangeInCapacity
      AutoScalingGroupName: !Ref rAutoScalingGroupApp
      Cooldown: "1"
      ScalingAdjustment: 1
  rAutoScalingUpApp:
    Type: AWS::AutoScaling::ScalingPolicy
    Properties:
      AdjustmentType: ChangeInCapacity
      AutoScalingGroupName: !Ref rAutoScalingGroupApp
      Cooldown: "1"
      ScalingAdjustment: -1
  rCWAlarmHighCPUApp:
    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 rAutoScalingDownApp
      Namespace: AWS/EC2
      Dimensions:
      - Name: AutoScalingGroupName
        Value: !Ref rAutoScalingGroupApp
      ComparisonOperator: GreaterThanThreshold
      MetricName: AppServerCpuHighUtilization
  rCWAlarmLowCPUApp:
    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 rAutoScalingUpApp
      Namespace: AWS/EC2
      Dimensions:
      - Name: AutoScalingGroupName
        Value: !Ref rAutoScalingGroupApp
      ComparisonOperator: LessThanThreshold
      MetricName: AppServerCpuLowUtilization
  rPostProcInstanceRole:
    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
            Resource:
            - '*'
          - Sid: CreateLoadBalancerListener
            Effect: Allow
            Action:
            - elasticloadbalancing:CreateLoadBalancerListeners
            Resource:
            - '*'
          - Sid: PublishNotificationTopic
            Effect: Allow
            Action:
            - sns:Publish
            Resource:
            - !Ref pSecurityAlarmTopic
          - Sid: SelfDestruct
            Effect: Allow
            Action:
            - ec2:TerminateInstances
            Resource:
            - '*'
  rPostProcInstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Path: /
      Roles:
      - !Ref rPostProcInstanceRole
  rPostProcInstance:
    Type: AWS::EC2::Instance
    DependsOn:
    - rAutoScalingGroupApp
    - rAutoScalingGroupWeb
    Properties:
      ImageId: !Ref pWebServerAMI
      InstanceType: !Ref pAppInstanceType
      IamInstanceProfile: !Ref rPostProcInstanceProfile
      SubnetId: !Ref pAppPrivateSubnetA
      SecurityGroupIds:
      - !Ref rSecurityGroupAppInstance
      UserData:
        Fn::Base64: !Sub |
          #!/bin/bash -xe

          echo Configure the region, necessary especially for GovCloud
          aws configure set 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
            echo *** Beginnning ELB HTTPS configuration ***
            echo Generating private key...
            openssl genrsa -out /tmp/my-private-key.pem 2048
            echo Creating CSR
            openssl req -sha256 -new -key /tmp/my-private-key.pem -out /tmp/csr.pem -subj "/C=US/ST=Washington/L=Seattle/O=NonProductionTestCert/CN=NonProductionTestCert"
            echo Self-signing certificate...
            openssl x509 -req -days 365 -in /tmp/csr.pem -signkey /tmp/my-private-key.pem -out /tmp/my-certificate.pem
            openssl rsa -in /tmp/my-private-key.pem -outform PEM
            echo Converting private key...
            openssl x509 -inform PEM -in /tmp/my-certificate.pem
            echo Uploading key to AWS IAM and saving ARN to environment variable...
            cert_arn=$(aws iam upload-server-certificate --server-certificate-name non-production-testing-server-cert --query 'ServerCertificateMetadata.Arn' --output text --certificate-body file:///tmp/my-certificate.pem --private-key file:///tmp/my-private-key.pem)
            echo Sleeping so IAM can propogate the certificate...
            sleep 10
            echo Removing key files...
            rm /tmp/*.pem
          fi
          echo Creating ELB HTTPS listener using the cert stored in the environment variable...
          aws elb create-load-balancer-listeners --load-balancer-name ${rELBWeb} --listeners "Protocol=HTTPS,LoadBalancerPort=443,InstanceProtocol=HTTP,InstancePort=80,SSLCertificateId=$cert_arn" --region ${AWS::Region}
          aws elb create-load-balancer-listeners --load-balancer-name ${rELBApp} --listeners "Protocol=HTTPS,LoadBalancerPort=443,InstanceProtocol=HTTP,InstancePort=80,SSLCertificateId=$cert_arn" --region ${AWS::Region}
          echo Send notification message...
          aws sns publish --topic-arn ${pSecurityAlarmTopic} \
            --subject "CloudFormation successfully launched ${AWS::StackName}" \
            --message "What now? Click here for more information: https://${rELBWeb.DNSName}/landing.html. Please note that the application server might be finishing up its initialization. If the link doesn't respond right away, please try it again in few minutes. This page resides on an application server in your new environment." \
            --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}
          echo *** ELB HTTPS configuration complete ***
      Tags:
      - Key: Name
        Value: PostProcessor
Outputs:
  LandingPageURL:
    Value: !Sub https://${rELBWeb.DNSName}/landing.html
    Description: Landing Page
  WebsiteURL:
    Value: !Sub https://${rELBWeb.DNSName}/wordpress/wp-admin/install.php
    Description: WordPress Website (demonstration purposes only)
  rSecurityGroupWeb:
    Value: !Ref rSecurityGroupWeb
  rSecurityGroupApp:
    Value: !Ref rSecurityGroupApp
  rSecurityGroupRDS:
    Value: !Ref rSecurityGroupRDS
  SurveyLink:
    Description: Please take a moment to complete the survey by clicking this link
    Value: https://aws.au1.qualtrics.com/SE/?SID=SV_55sYYdtY1NhTTgN&qs=${COMPLIANCE_SURVEY_LINK}
  Help:
    Description: For assistance or questions regarding this quickstart please email
      compliance-accelerator@amazon.com
    Value: ''
...