--- 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: '' ...