sunapsis_4_web.yaml
---

CloudFormation template for Sunapsis web/application servers (4 of 5)

  • Auto Scaling Group
    • Associated Launch Configuration
      • Bootstraps w/user data - configures as web server, installs software, schedules tasks
    • Security groups
      • Also ingress rules updating RDS SQL & SoftNAS security groups allowing web server access
  • ELBv2 (ALB) with target group associated with ASG
  • IAM policies, role, instance profile
  • CloudWatch Log Group (for IIS logs)
  • Route53 aliases for ELB & individual web servers
AWSTemplateFormatVersion: 2010-09-09 Description: Sunapsis (web/app)

Parameters

These are the input parameters for this template. All of these parameters must be supplied for this template to be deployed.

Parameters: EnvironmentType: Description: Environment type of this resource (dev, tst, rpt, trn, prd) Type: String Default: prd AllowedValues: - dev - tst - rpt - trn - prd WebServerInstanceType: Description: Instance type to use for EC2 instances Type: String Default: t2.medium WindowsAmiParamStore: Description: Parameter Store entry for latest Windows 2012 R2 Base image Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id> Default: /aws/service/ami-windows-latest/Windows_Server-2012-R2_RTM-English-64Bit-Base SSLCertARN:
  • ua-uits-general-nonprod: arn:aws:acm:us-west-2:722748364533:certificate/81531e4c-26eb-40a9-924c-a40af008b349
  • ua-erp: arn:aws:acm:us-west-2:760232551367:certificate/35dbb050-1f99-4c73-adfa-dcd0aff0eee8
  • sls-prod: arn:aws:acm:us-west-2:918461542486:certificate/5501cdd4-a93e-4c8a-8a7e-72641d6b4302
Description: ARN of the SSL certificate to use for the ELB Type: String Default: arn:aws:acm:us-west-2:918461542486:certificate/5501cdd4-a93e-4c8a-8a7e-72641d6b4302 ### Tags TagService: Description: Name of the service associated with this resource (as listed in the service catalog) Type: String Default: Sunapsis TagContactNetID: Description: NetID of the primary technical resource Type: String Default: dbaty TagTicketNumber: Description: Ticket number for the CLOUD Jira project Type: String Default: CLOUD-85 TagAccountNumber: Description: Account number associated with the service Type: String Default: 2433643 TagSubAccount: Description: Sub account associated with the service Type: String Default: Sunapsis

Metadata

Metadata is mostly for organizing and presenting Parameters in a better way when using CloudFormation in the AWS Web UI.

Metadata: AWS::CloudFormation::Interface: ParameterGroups: - Label: default: Environment Parameters: - EnvironmentType - Label: default: Settings Parameters: - WebServerInstanceType - WindowsAmiParamStore - SSLCertARN - Label: default: Tags Parameters: - TagService - TagContactNetID - TagTicketNumber - TagAccountNumber - TagSubAccount ParameterLabels: {}

Conditions

Establishes conditions based on input parameters.

Conditions: IsNotPRD: !Not [!Equals [!Ref EnvironmentType, prd ]]

Resources

Resources:

EC2 Security Groups

EC2 Security Group for the Elastic Load Balancer (ELB)

SecurityGroupForELB: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: !Sub EC2 Security Group for Sunapsis ELB (${EnvironmentType}) VpcId: Fn::ImportValue: !Sub sunapsis-${EnvironmentType}-vpc SecurityGroupIngress: - IpProtocol: tcp FromPort: 80 ToPort: 80 CidrIp: 0.0.0.0/0 Description: Allow all HTTP - IpProtocol: tcp FromPort: 443 ToPort: 443 CidrIp: 0.0.0.0/0 Description: Allow all HTTPS Tags: - Key: Name Value: !Sub sunapsis-${EnvironmentType}-sg-web-elb - Key: environment Value: !Ref EnvironmentType - Key: contactnetid Value: !Ref TagContactNetID - Key: ticketnumber Value: !Ref TagTicketNumber - Key: accountnumber Value: !Ref TagAccountNumber - Key: service Value: !Ref TagService - Key: subaccount Value: !Ref TagSubAccount

EC2 Security Group for the web servers

SecurityGroupForWebServers: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: !Sub EC2 Security Group for Sunapsis web servers (${EnvironmentType}) VpcId: Fn::ImportValue: !Sub sunapsis-${EnvironmentType}-vpc SecurityGroupIngress: - IpProtocol: tcp FromPort: 3389 ToPort: 3389 CidrIp: 150.135.112.64/27 Description: InfraDev VPN (RDP) - IpProtocol: tcp FromPort: 3389 ToPort: 3389 CidrIp: 150.135.112.96/27 Description: EntApp VPN (RDP) - IpProtocol: tcp FromPort: 8500 ToPort: 8500 CidrIp: 150.135.112.64/27 Description: InfraDev VPN (ColdFusion admin) - IpProtocol: tcp FromPort: 443 ToPort: 443 SourceSecurityGroupId: !Ref SecurityGroupForELB Tags: - Key: Name Value: !Sub sunapsis-${EnvironmentType}-sg-web-ec2 - Key: environment Value: !Ref EnvironmentType - Key: contactnetid Value: !Ref TagContactNetID - Key: ticketnumber Value: !Ref TagTicketNumber - Key: accountnumber Value: !Ref TagAccountNumber - Key: service Value: !Ref TagService - Key: subaccount Value: !Ref TagSubAccount

EC2 Security Group Ingress to update the existing Security Group for RDS to allow access from web servers

SecurityGroupIngressWebToSQL: Type: AWS::EC2::SecurityGroupIngress Properties: GroupId: Fn::ImportValue: !Sub sunapsis-${EnvironmentType}-RdsSecurityGroupId IpProtocol: tcp FromPort: 1433 ToPort: 1433 SourceSecurityGroupId: !Ref SecurityGroupForWebServers Description: Allow SQL access from web servers

EC2 Security Group Ingress to update the existing Security Group for SoftNAS to allow access from web servers

SecurityGroupIngressWebToSoftNasForNetBiosTcp: Type: AWS::EC2::SecurityGroupIngress Properties: GroupId: Fn::ImportValue: !Sub sunapsis-${EnvironmentType}-SoftNasSecurityGroupId IpProtocol: tcp FromPort: 137 ToPort: 139 SourceSecurityGroupId: !Ref SecurityGroupForWebServers Description: Allow NetBIOS TCP to SoftNAS from web servers SecurityGroupIngressWebToSoftNasForNetBiosUdp: Type: AWS::EC2::SecurityGroupIngress Properties: GroupId: Fn::ImportValue: !Sub sunapsis-${EnvironmentType}-SoftNasSecurityGroupId IpProtocol: udp FromPort: 137 ToPort: 139 SourceSecurityGroupId: !Ref SecurityGroupForWebServers Description: Allow NetBIOS UDP to SoftNAS from web servers SecurityGroupIngressWebToSoftNasForSMB: Type: AWS::EC2::SecurityGroupIngress Properties: GroupId: Fn::ImportValue: !Sub sunapsis-${EnvironmentType}-SoftNasSecurityGroupId IpProtocol: tcp FromPort: 445 ToPort: 445 SourceSecurityGroupId: !Ref SecurityGroupForWebServers Description: Allow SMB to SoftNAS from web servers

Auto Scaling Groups

 - includes ASG, Launch Config & Scheduled Actions

Launch Configuration

LaunchConfig: Type: AWS::AutoScaling::LaunchConfiguration Properties: ImageId: !Ref WindowsAmiParamStore KeyName: Fn::ImportValue: !Sub sunapsis-${EnvironmentType}-ec2-keypair InstanceType: !Ref WebServerInstanceType InstanceMonitoring: true AssociatePublicIpAddress: false BlockDeviceMappings: - DeviceName: /dev/sda1 Ebs: VolumeType: gp2 DeleteOnTermination: true VolumeSize: 80 - DeviceName: xvdd Ebs: VolumeType: gp2 DeleteOnTermination: true Encrypted: true VolumeSize: 80

Can't do this in Launch Configs (as of April 2018), leaving it for hopeful future use, but right now this is done via user data CreditSpecification:

   - CPUCredits: unlimited
IamInstanceProfile: !Ref IAMProfileWebServer SecurityGroups: - !Ref SecurityGroupForWebServers UserData: Fn::Base64: !Sub - | <powershell> mkdir D:\bootstrap $( try {

Variables that get populated by CloudFormation need to be in the main user data script

$environmentName = "${EnvironmentType}" $s3bucketPrefix = "${BucketNamePrefix}" $serviceName = "Sunapsis" $rootBootstrap = "D:\bootstrap" $serverType = "WebServer" $s3keyPowerShell = "\BootstrapScripts\" $s3bucketApp = $s3bucketPrefix + "-app" $s3bucketArchive = $s3bucketPrefix + "-archive"

Download PowerShell bootstrap script from S3

Read-S3Object -BucketName $s3bucketApp -Folder $rootBootstrap -KeyPrefix $s3keyPowerShell

Run bootstrap script (dot-sourced)

. (Join-Path -Path $rootBootstrap -ChildPath "sunapsisBuild$($serverType).ps1") } catch {

Create error files

Set-Content (Join-Path -Path $rootBootstrap -ChildPath "bootstrap-error-exception.txt") -Value $Error[0].Exception $Error[0] | Export-Clixml -Path (Join-Path -Path $rootBootstrap -ChildPath "bootstrap-error-complete.xml")

Get the EC2 instance ID

$awsInstanceId = Invoke-RestMethod -Uri "http://169.254.169.254/latest/meta-data/instance-id"

Retrieve email configuration from Systems Manager Parameter Store

$emailSMTPServer = (Get-SSMParameterValue -Name "sunapsisSMTPServer").Parameters.Value $emailSMTPUsername = (Get-SSMParameterValue -Name "sunapsisSMTPUsername" -WithDecryption $True).Parameters.Value $emailSMTPPassword = (Get-SSMParameterValue -Name "sunapsisSMTPPassword" -WithDecryption $True).Parameters.Value $emailTo = (Get-SSMParameterValue -Name "sunapsisEmailTo").Parameters.Value $emailFrom = (Get-SSMParameterValue -Name "sunapsisEmailFrom").Parameters.Value

Finish email config

$emailCred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $emailSMTPUsername, ($emailSMTPPassword | ConvertTo-SecureString -asPlainText -Force) $emailSubject = "(AWS-" + $serviceName + "-" + $environmentName.ToUpper() + ") " + $serverType + " deployment FAILURE - " + $awsInstanceId $emailBody = $serverType + " deployment failure on instance " + $awsInstanceId + " in environment " + $environmentName.ToUpper() + " at: " + (Get-Date).ToUniversalTime().ToString("G") + " (UTC)`r`n`r`nSee attached file for full error details.`r`n`r`n" + $Error[0].InvocationInfo.PositionMessage + "`r`n`r`n" + $Error[0].Exception

Send email

Send-MailMessage -To ($emailTo.Split(",")) -From $emailFrom -Subject $emailSubject -Body $emailBody -Credential $emailCred -SmtpServer $emailSMTPServer -UseSSL -Attachments (Join-Path -Path $rootBootstrap -ChildPath "bootstrap-error-complete.xml") throw } ) *> D:\bootstrap\bootstrap-output.txt </powershell> - BucketNamePrefix: Fn::ImportValue: !Sub sunapsis-${EnvironmentType}-bucketname-prefix

Auto Scaling Group

WebServerAutoScalingGroup: Type: AWS::AutoScaling::AutoScalingGroup Properties: Cooldown: 900 HealthCheckGracePeriod: 0 HealthCheckType: EC2 LaunchConfigurationName: !Ref LaunchConfig MinSize: 2 MaxSize: 2 TargetGroupARNs: - !Ref ElbTargetGroup MetricsCollection: - Granularity: 1Minute VPCZoneIdentifier: - Fn::ImportValue: !Sub sunapsis-${EnvironmentType}-private-subnet-a - Fn::ImportValue: !Sub sunapsis-${EnvironmentType}-private-subnet-b NotificationConfigurations: - TopicARN: !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:sunapsis-${EnvironmentType} NotificationTypes: - autoscaling:EC2_INSTANCE_LAUNCH - autoscaling:EC2_INSTANCE_LAUNCH_ERROR - autoscaling:EC2_INSTANCE_TERMINATE - autoscaling:EC2_INSTANCE_TERMINATE_ERROR Tags: - Key: Name Value: !Sub sunapsis-${EnvironmentType}-web-newly-launched PropagateAtLaunch: true # We set a resource-specific Name tag as part of the bootstrap process. However it is a required tag and this is effectively a placeholder to accomodate policy. - Key: environment Value: !Ref EnvironmentType PropagateAtLaunch: true - Key: contactnetid Value: !Ref TagContactNetID PropagateAtLaunch: true - Key: ticketnumber Value: !Ref TagTicketNumber PropagateAtLaunch: true - Key: accountnumber Value: !Ref TagAccountNumber PropagateAtLaunch: true - Key: service Value: !Ref TagService PropagateAtLaunch: true - Key: subaccount Value: !Ref TagSubAccount PropagateAtLaunch: true

Scheduled Actions

ScheduledActionUp: Type: "AWS::AutoScaling::ScheduledAction" Condition: IsNotPRD Properties: AutoScalingGroupName: !Ref WebServerAutoScalingGroup MaxSize: 2 MinSize: 2 Recurrence: "30 9 * * MON-FRI" # Mo-Fr, 7:30pm UTC (M-F, 2:30am AZ) ScheduledActionDown: Type: "AWS::AutoScaling::ScheduledAction" Condition: IsNotPRD Properties: AutoScalingGroupName: !Ref WebServerAutoScalingGroup MaxSize: 0 MinSize: 0 Recurrence: "30 1 * * TUE-SAT" # Tu-Sa, 1:30am UTC (M-F, 6:30pm AZ) - needs run "TUE-SAT" as UTC-7 (AZ) crosses the midnight threshold

Elastic Load Balancers (ELB) v2

 - Includes ELB, target group & listeners

ELB itself

WebServerLoadBalancer: Type: AWS::ElasticLoadBalancingV2::LoadBalancer Properties: Name: !Sub sunapsis-${EnvironmentType}-web-elb Scheme: internet-facing LoadBalancerAttributes: - Key: idle_timeout.timeout_seconds Value: 180 - Key: access_logs.s3.enabled Value: true - Key: access_logs.s3.bucket Value: edu.arizona.iso.elb.logs - Key: access_logs.s3.prefix Value: !Sub sunapsis-${EnvironmentType}-app Subnets: - Fn::ImportValue: !Sub sunapsis-${EnvironmentType}-public-subnet-a - Fn::ImportValue: !Sub sunapsis-${EnvironmentType}-public-subnet-b SecurityGroups: - !Ref SecurityGroupForELB Tags: - Key: Name Value: !Sub sunapsis-${EnvironmentType}-elb - Key: environment Value: !Ref EnvironmentType - Key: contactnetid Value: !Ref TagContactNetID - Key: ticketnumber Value: !Ref TagTicketNumber - Key: accountnumber Value: !Ref TagAccountNumber - Key: service Value: !Ref TagService - Key: subaccount Value: !Ref TagSubAccount

ELB Target Group

ElbTargetGroup: Type: AWS::ElasticLoadBalancingV2::TargetGroup Properties: HealthCheckIntervalSeconds: 30 UnhealthyThresholdCount: 2 HealthCheckPath: / Name: !Sub sunapsis-${EnvironmentType}-web-tg Port: 443 Protocol: HTTPS VpcId: Fn::ImportValue: !Sub sunapsis-${EnvironmentType}-vpc TargetGroupAttributes: - Key: deregistration_delay.timeout_seconds Value: 60 - Key: stickiness.enabled Value: true - Key: stickiness.type Value: lb_cookie - Key: stickiness.lb_cookie.duration_seconds Value: 3600 Tags: - Key: Name Value: !Sub sunapsis-${EnvironmentType}-tg - Key: environment Value: !Ref EnvironmentType - Key: contactnetid Value: !Ref TagContactNetID - Key: ticketnumber Value: !Ref TagTicketNumber - Key: accountnumber Value: !Ref TagAccountNumber - Key: service Value: !Ref TagService - Key: subaccount Value: !Ref TagSubAccount

ELB Listeners

ElbListenerHTTP: Type: AWS::ElasticLoadBalancingV2::Listener Properties: DefaultActions: - Type: forward TargetGroupArn: !Ref ElbTargetGroup LoadBalancerArn: !Ref WebServerLoadBalancer Port: 80 Protocol: HTTP ElbListenerHTTPS: Type: AWS::ElasticLoadBalancingV2::Listener Properties: Certificates: - CertificateArn: !Ref SSLCertARN DefaultActions: - Type: forward TargetGroupArn: !Ref ElbTargetGroup LoadBalancerArn: !Ref WebServerLoadBalancer Port: 443 Protocol: HTTPS SslPolicy: ELBSecurityPolicy-TLS-1-1-2017-01

CloudWatch Logs

 - Includes Log Group

CloudWatch Log Group

WebServerLogGroup: Type: AWS::Logs::LogGroup Properties: LogGroupName: !Sub sunapsis-${EnvironmentType}-webserver RetentionInDays: 30

IAM

 - Includes roles, instance profiles & policies

IAM Roles

IAMRoleWebServer: Type: AWS::IAM::Role Properties: RoleName: !Sub sunapsis-${EnvironmentType}-role-webserver AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - ec2.amazonaws.com Action: - sts:AssumeRole Path: /

IAM Instance Profilesyssm

IAMProfileWebServer: Type: AWS::IAM::InstanceProfile Properties: Path: / Roles: - !Ref IAMRoleWebServer

IAM Policies

IAMPolicyAppS3Bucket: Type: AWS::IAM::Policy Properties: PolicyName: !Sub sunapsis-${EnvironmentType}-policy-s3bucket-app-read PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - s3:ListBucket - s3:GetBucketLocation Resource: !Sub - "arn:aws:s3:::${S3BucketApp}" - S3BucketApp: Fn::ImportValue: !Sub sunapsis-${EnvironmentType}-bucket-app - Effect: Allow Action: - s3:GetObjectMetaData - s3:GetObject Resource: !Sub - "arn:aws:s3:::${S3BucketApp}/*" - S3BucketApp: Fn::ImportValue: !Sub sunapsis-${EnvironmentType}-bucket-app Roles: - !Ref IAMRoleWebServer IAMPolicyArchiveS3Bucket: Type: AWS::IAM::Policy Properties: PolicyName: !Sub sunapsis-${EnvironmentType}-policy-s3bucket-archive-read-write PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - s3:ListBucket - s3:GetBucketLocation Resource: !Sub - "arn:aws:s3:::${S3BucketArchive}" - S3BucketArchive: Fn::ImportValue: !Sub sunapsis-${EnvironmentType}-bucket-archive - Effect: Allow Action: - s3:GetObjectMetaData - s3:GetObject - s3:PutObject Resource: !Sub - "arn:aws:s3:::${S3BucketArchive}/*" - S3BucketArchive: Fn::ImportValue: !Sub sunapsis-${EnvironmentType}-bucket-archive Roles: - !Ref IAMRoleWebServer IAMPolicyUpdateRoute53: Type: AWS::IAM::Policy Properties: PolicyName: !Sub sunapsis-${EnvironmentType}-policy-update-route53 PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - cloudformation:DescribeStacks - route53:ChangeResourceRecordSets - route53:ListHostedZones Resource: - "*" Roles: - !Ref IAMRoleWebServer IAMPolicyTagEC2: Type: AWS::IAM::Policy Properties: PolicyName: !Sub sunapsis-${EnvironmentType}-policy-ec2-tagging PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - ec2:DescribeTags Resource: - "*" - Effect: Allow Action: - ec2:CreateTags - ec2:AssociateAddress Resource: - "*" Condition: StringLike: ec2:ResourceTag/Name: sunapsis* Roles: - !Ref IAMRoleWebServer IAMPolicyCpuCreditEc2: Type: AWS::IAM::Policy Properties: PolicyName: !Sub sunapsis-${EnvironmentType}-policy-ec2-cpu-credit PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - ec2:ModifyInstanceCreditSpecification Resource: - "*" Roles: - !Ref IAMRoleWebServer IAMPolicySSM: Type: AWS::IAM::Policy Properties: PolicyName: !Sub sunapsis-${EnvironmentType}-policy-ssm PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - ssm:ListAssociations - ssm:ListInstanceAssociations - ssm:UpdateInstanceInformation - ec2:DescribeInstanceStatus - ec2messages:GetMessages - ec2messages:AcknowledgeMessage Resource: - "*" - Effect: Allow Action: - ssm:GetParameters Resource: - !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/sunapsis*" Roles: - !Ref IAMRoleWebServer IAMPolicyDecryptKMS: Type: AWS::IAM::Policy Properties: PolicyName: !Sub sunapsis-${EnvironmentType}-policy-kms PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - kms:Decrypt Resource: - !Sub arn:aws:kms:${AWS::Region}:${AWS::AccountId}:key/alias/aws/ssm Roles: - !Ref IAMRoleWebServer IAMPolicyCloudWatchLogs: Type: AWS::IAM::Policy Properties: PolicyName: !Sub sunapsis-${EnvironmentType}-policy-cloudwatch-logs PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - logs:CreateLogGroup - logs:CreateLogStream - logs:DescribeLogGroups - logs:DescribeLogStreams - logs:PutLogEvents Resource: - arn:aws:logs:*:*:* Roles: - !Ref IAMRoleWebServer

Route53

Route53 Record Set Group

Route53RecordSetGroup: Type: AWS::Route53::RecordSetGroup Properties: HostedZoneName: Fn::ImportValue: !Sub sunapsis-${EnvironmentType}-hostedzone-name Comment: !Sub Sunapsis (${EnvironmentType}) - aliases RecordSets:

Alias for ELB

- Name: Fn::ImportValue: !Sub sunapsis-${EnvironmentType}-fqdn-for-elb Type: A AliasTarget: HostedZoneId: !GetAtt WebServerLoadBalancer.CanonicalHostedZoneID DNSName: !GetAtt WebServerLoadBalancer.DNSName

Placeholder record set for the web server alias in us-west-2a (actual value gets set as part of user data script)

- Name: Fn::ImportValue: !Sub sunapsis-${EnvironmentType}-fqdn-for-web-a Type: A TTL: 300 ResourceRecords: - 127.0.0.1

Placeholder record set for the web server alias in us-west-2b (actual value gets set as part of user data script)

- Name: Fn::ImportValue: !Sub sunapsis-${EnvironmentType}-fqdn-for-web-b Type: A TTL: 300 ResourceRecords: - 127.0.0.1