d2lcsr_4_web.yaml
---

D2L Course Site Request - CloudFormation Template (4 of 4)

Deploys D2L Course Site Request remaining infrastructure.

  • assumes existance of:

    S3 bucket (template 1) RDS SQL instance (template 2) CloudFront Distribution (template 3)

AWSTemplateFormatVersion: 2010-09-09 Description: D2L Course Site Request (web & other)

Parameters

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

Parameters:

ScheduledTasksDisabled is a flag used to determine if scheduled tasks should be disabled when task server is created

ScheduledTasksDisabled: Description: Disables scheduled tasks on Task Server creation (true/false) Type: String Default: false AllowedValues: - true - false

S3StackName is the name of the stack that created the S3 buckets

S3StackName: Description: Name of the stack that created the S3 buckets (template 1 of 4) Type: String Default: d2lcsr-prd-s3

RdsStackName is the name of the stack that created the RDS SQL Server instance

RdsStackName: Description: Name of the stack that created the RDS SQL Server instance (template 2 of 4) Type: String Default: d2lcsr-prd-rds

CloudFrontStackName is the name of the stack that created the CloudFront Distribution

CloudFrontStackName: Description: Name of the stack that created the CloudFront Distribution (template 3 of 4) Type: String Default: d2lcsr-prd-cloudfront

BucketNamePrefix is the prefix of the name of the S3 buckets

BucketNamePrefix: Description: Prefix of the name of the S3 buckets (environment type will be appended) Type: String Default: edu-arizona-sls-prod-d2lcsr

Ec2KeyPairName is the EC2 KeyPair name used for instance launch (req'd to retrieve Windows admin password)

Ec2KeyPairName: Description: EC2 KeyPair name used for instance launch (req'd to retrieve Windows admin password) Type: AWS::EC2::KeyPair::KeyName Default: uits-eis-keypair

EmailForSNSSubscription is the email address to use for the SNS subscription

  • sls-nonprod: d2lcsr-aws-nonprod@list.arizona.edu
  • sls-prod: d2lcsr-aws-prod@list.arizona.edu
EmailForSNSSubscription: Description: Email address to use for the SNS subscription Type: String Default: d2lcsr-aws-prod@list.arizona.edu

InstanceTypeTask is the EC2 instance type for the task server

InstanceTypeTask: Description: Instance type for the task server Type: String Default: t3.small

InstanceTypeWeb is the EC2 instance type for the web servers

InstanceTypeWeb: Description: Instance type for the web servers Type: String Default: t3.micro

VPCID is the ID of the VPC where this template will be deployed. -sls-nonprod: vpc-2dc9b34a -sls-prod: vpc-82ee9de5

VPCID: Description: Target VPC Type: AWS::EC2::VPC::Id Default: vpc-82ee9de5

PrivateSubnetA is the private Subnet ID for us-west-2a -sls-nonprod: subnet-49338600 -sls-prod: subnet-fd862bb4

PrivateSubnetA: Description: Private Subnet (us-west-2a) Type: AWS::EC2::Subnet::Id Default: subnet-fd862bb4

PrivateSubnetB is the private Subnet ID for us-west-2b -sls-nonprod: subnet-7f019218 -sls-prod: subnet-ca49c2ad

PrivateSubnetB: Description: Private Subnet (us-west-2b) Type: AWS::EC2::Subnet::Id Default: subnet-ca49c2ad

PublicSubnetA is the public Subnet ID for us-west-2a -sls-nonprod: subnet-48338601 -sls-prod: subnet-fc862bb5

PublicSubnetA: Description: Public Subnet (us-west-2a) Type: AWS::EC2::Subnet::Id Default: subnet-fc862bb5

PublicSubnetB is the public Subnet ID for us-west-2b -sls-nonprod: subnet-fc862bb5 -sls-prod: subnet-cb49c2ac

PublicSubnetB: Description: Public Subnet (us-west-2b) Type: AWS::EC2::Subnet::Id Default: subnet-cb49c2ac

AsgAZs is the list of AZs to use for the Auto Scaling Groups

AsgAZs: Description: Comma delimited list of Availability Zones to use for the Auto Scaling Groups (MAX 2) Type: List<AWS::EC2::AvailabilityZone::Name> Default: us-west-2a,us-west-2b

SSLCertARN is the ARN of the SSL certificate to use for the ELB

  • sls-nonprod: arn:aws:acm:us-west-2:776095071695:certificate/14d7a136-05d8-47ec-a917-7cc6fba2c007
  • sls-prod: arn:aws:acm:us-west-2:918461542486:certificate/df1e7a1a-8413-4f57-9c69-baac375a06e3
SSLCertARN: Description: ARN of the SSL certificate to use for the ELB (US-West-2) Type: String Default: arn:aws:acm:us-west-2:918461542486:certificate/df1e7a1a-8413-4f57-9c69-baac375a06e3 WindowsAmiParamStore: Description: Parameter Store entry for latest Windows 2022 base image Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id> Default: /aws/service/ami-windows-latest/Windows_Server-2022-English-Full-Base AllowedValues: - /aws/service/ami-windows-latest/Windows_Server-2022-English-Full-Base ### Tags TagService: Description: Name of the service associated with this resource (as listed in the service catalog) Type: String Default: D2L Course Site Request TagEnvironment: Description: Environment type of this resource (dev, tst, rpt, trn, prd) Type: String Default: prd AllowedValues: - dev - tst - rpt - trn - prd TagContactNetID: Description: NetID of the primary technical resource Type: String Default: dbaty TagTicketNumber: Description: Ticket number of the associated Type: String Default: CLOUD-76 TagAccountNumber: Description: Account number associated with the service Type: String Default: 2433643 # Learning Management Systems TagSubAccount: Description: Sub account associated with the service Type: String Default: D2L

Metadata

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

Metadata: AWS::CloudFormation::Interface: ParameterGroups: - Label: default: Settings Parameters: - ScheduledTasksDisabled - S3StackName - RdsStackName - CloudFrontStackName - BucketNamePrefix - Ec2KeyPairName - EmailForSNSSubscription - InstanceTypeTask - InstanceTypeWeb - WindowsAmiParamStore - VPCID - PrivateSubnetA - PrivateSubnetB - PublicSubnetA - PublicSubnetB - AsgAZs - SSLCertARN - Label: default: Tags Parameters: - TagService - TagEnvironment - TagContactNetID - TagTicketNumber - TagAccountNumber - TagSubAccount ParameterLabels: {}

Conditions

Establishes conditions based on input parameters.

Conditions: IsPRD: !Equals [ !Ref TagEnvironment, prd ] IsNotPRD: !Not [!Equals [!Ref TagEnvironment, prd ]]

Resources

Wow. This is a lot of resources to deploy. Do you really need them all?

Resources:

EC2 Security Groups

EC2 Security Groups for the EC2 instances & ELB

SecurityGroupForTaskServer: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: !Sub D2L Course Site Request - scheduled task server (${TagEnvironment}) VpcId: !Ref VPCID SecurityGroupIngress: - IpProtocol: tcp FromPort: 3389 ToPort: 3389 CidrIp: 150.135.112.64/27 # InfraDev VPN - IpProtocol: tcp FromPort: 3389 ToPort: 3389 CidrIp: 150.135.112.96/27 # EntApp VPN - IpProtocol: tcp FromPort: 445 ToPort: 445 CidrIp: 150.135.112.64/27 # InfraDev VPN - IpProtocol: tcp FromPort: 445 ToPort: 445 CidrIp: 150.135.112.96/27 # EntApp VPN Tags: - Key: Name Value: !Sub d2lcsr-${TagEnvironment}-sg-task - Key: environment Value: !Ref TagEnvironment - Key: contactnetid Value: !Ref TagContactNetID - Key: ticketnumber Value: !Ref TagTicketNumber - Key: accountnumber Value: !Ref TagAccountNumber - Key: subaccount Value: !Ref TagSubAccount - Key: service Value: !Ref TagService

EC2 Security Group for the Elastic Load Balancer (ELB)

SecurityGroupForELB: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: !Sub D2L Course Site Request - ELB (${TagEnvironment}) VpcId: !Ref VPCID SecurityGroupIngress: - 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: !Sub d2lcsr-${TagEnvironment}-sg-elb - Key: environment Value: !Ref TagEnvironment - Key: contactnetid Value: !Ref TagContactNetID - Key: ticketnumber Value: !Ref TagTicketNumber - Key: accountnumber Value: !Ref TagAccountNumber - Key: subaccount Value: !Ref TagSubAccount - Key: service Value: !Ref TagService

EC2 Security Group for the web servers

SecurityGroupForWebServers: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: !Sub D2L Course Site Request - web servers (${TagEnvironment}) VpcId: !Ref VPCID SecurityGroupIngress: - IpProtocol: tcp FromPort: 3389 ToPort: 3389 CidrIp: 150.135.112.64/27 # InfraDev VPN - IpProtocol: tcp FromPort: 3389 ToPort: 3389 CidrIp: 150.135.112.96/27 # EntApp VPN - IpProtocol: tcp FromPort: 80 ToPort: 80 SourceSecurityGroupId: !Ref SecurityGroupForELB - IpProtocol: tcp FromPort: 80 ToPort: 80 CidrIp: 150.135.112.64/27 # InfraDev VPN - IpProtocol: tcp FromPort: 80 ToPort: 80 CidrIp: 150.135.112.96/27 # EntApp VPN Tags: - Key: Name Value: !Sub d2lcsr-${TagEnvironment}-sg-web - Key: environment Value: !Ref TagEnvironment - Key: contactnetid Value: !Ref TagContactNetID - Key: ticketnumber Value: !Ref TagTicketNumber - Key: accountnumber Value: !Ref TagAccountNumber - Key: subaccount Value: !Ref TagSubAccount - Key: service Value: !Ref TagService

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

SecurityGroupIngressWebToSQL: Type: AWS::EC2::SecurityGroupIngress Properties: GroupId: Fn::ImportValue: !Sub ${RdsStackName}-RdsSecurityGroupId IpProtocol: tcp FromPort: 1433 ToPort: 1433 SourceSecurityGroupId: !Ref SecurityGroupForWebServers SecurityGroupIngressTaskToSQL: Type: AWS::EC2::SecurityGroupIngress Properties: GroupId: Fn::ImportValue: !Sub ${RdsStackName}-RdsSecurityGroupId IpProtocol: tcp FromPort: 1433 ToPort: 1433 SourceSecurityGroupId: !Ref SecurityGroupForTaskServer

Elastic Load Balancers (ELB) v2

 - Includes ELB, target group & listeners

ELB itself

WebServerLoadBalancer: Type: AWS::ElasticLoadBalancingV2::LoadBalancer Properties: Name: !Sub d2lcsr-${TagEnvironment}-elb Scheme: internet-facing LoadBalancerAttributes: - Key: idle_timeout.timeout_seconds Value: 300 Subnets: - Ref: PublicSubnetA - Ref: PublicSubnetB SecurityGroups: - Ref: SecurityGroupForELB Tags: - Key: Name Value: !Sub d2lcsr-${TagEnvironment}-elb - Key: environment Value: !Ref TagEnvironment - Key: contactnetid Value: !Ref TagContactNetID - Key: ticketnumber Value: !Ref TagTicketNumber - Key: accountnumber Value: !Ref TagAccountNumber - Key: subaccount Value: !Ref TagSubAccount - Key: service Value: !Ref TagService

ELB Target Group

ElbTargetGroup: Type: AWS::ElasticLoadBalancingV2::TargetGroup Properties: HealthCheckIntervalSeconds: 30 UnhealthyThresholdCount: 2 HealthCheckPath: / Name: !Sub d2lcsr-${TagEnvironment}-tg Port: 80 Protocol: HTTP VpcId: Ref: VPCID 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 d2lcsr-${TagEnvironment}-tg - Key: environment Value: !Ref TagEnvironment - Key: contactnetid Value: !Ref TagContactNetID - Key: ticketnumber Value: !Ref TagTicketNumber - Key: accountnumber Value: !Ref TagAccountNumber - Key: subaccount Value: !Ref TagSubAccount - Key: service Value: !Ref TagService

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

CloudWatch Logs

 - Includes Log Group

CloudWatch Log Group

LogGroup: Type: AWS::Logs::LogGroup Properties: LogGroupName: !Sub d2lcsr-${TagEnvironment} RetentionInDays: 30

Auto Scaling Groups

 - includes ASG, Scaling Policy, Launch Config & CW Alarms

Auto Scaling Groups

WebServerAutoScalingGroup: Type: AWS::AutoScaling::AutoScalingGroup Properties: Cooldown: 1800 HealthCheckGracePeriod: 0 HealthCheckType: EC2 LaunchConfigurationName: !Ref LaunchConfigWeb MinSize: !If [ IsPRD, 2, 1 ] MaxSize: !If [ IsPRD, 6, 3 ] TargetGroupARNs: - !Ref ElbTargetGroup MetricsCollection: - Granularity: 1Minute VPCZoneIdentifier: - !Ref PrivateSubnetA - !Ref PrivateSubnetB NotificationConfigurations: - TopicARN: !Ref SNSTopic NotificationTypes: - autoscaling:EC2_INSTANCE_LAUNCH - autoscaling:EC2_INSTANCE_LAUNCH_ERROR - autoscaling:EC2_INSTANCE_TERMINATE - autoscaling:EC2_INSTANCE_TERMINATE_ERROR Tags: - Key: Name Value: !Sub d2lcsr-${TagEnvironment}-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 TagEnvironment PropagateAtLaunch: true - Key: contactnetid Value: !Ref TagContactNetID PropagateAtLaunch: true - Key: ticketnumber Value: !Ref TagTicketNumber PropagateAtLaunch: true - Key: accountnumber Value: !Ref TagAccountNumber PropagateAtLaunch: true - Key: subaccount Value: !Ref TagSubAccount PropagateAtLaunch: true - Key: service Value: !Ref TagService PropagateAtLaunch: true TaskServerAutoScalingGroup: Type: AWS::AutoScaling::AutoScalingGroup Properties: Cooldown: 900 HealthCheckGracePeriod: 0 HealthCheckType: EC2 LaunchConfigurationName: !Ref LaunchConfigTask MinSize: 1 MaxSize: 1 VPCZoneIdentifier: - !Ref PrivateSubnetA - !Ref PrivateSubnetB NotificationConfigurations: - TopicARN: !Ref SNSTopic NotificationTypes: - autoscaling:EC2_INSTANCE_LAUNCH - autoscaling:EC2_INSTANCE_LAUNCH_ERROR - autoscaling:EC2_INSTANCE_TERMINATE - autoscaling:EC2_INSTANCE_TERMINATE_ERROR Tags: - Key: Name Value: !Sub d2lcsr-${TagEnvironment}-task-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 TagEnvironment PropagateAtLaunch: true - Key: contactnetid Value: !Ref TagContactNetID PropagateAtLaunch: true - Key: ticketnumber Value: !Ref TagTicketNumber PropagateAtLaunch: true - Key: accountnumber Value: !Ref TagAccountNumber PropagateAtLaunch: true - Key: subaccount Value: !Ref TagSubAccount PropagateAtLaunch: true - Key: service Value: !Ref TagService PropagateAtLaunch: true

Launch Configurations

LaunchConfigTask: Type: AWS::AutoScaling::LaunchConfiguration Properties: ImageId: !Ref WindowsAmiParamStore KeyName: !Ref Ec2KeyPairName InstanceType: !Ref InstanceTypeTask InstanceMonitoring: true SecurityGroups: - Ref: SecurityGroupForTaskServer IamInstanceProfile: !Ref IAMProfileTaskServer BlockDeviceMappings: - DeviceName: /dev/sda1 Ebs: VolumeType: gp2 DeleteOnTermination: true VolumeSize: 80 - DeviceName: xvdd Ebs: VolumeType: gp2 DeleteOnTermination: true VolumeSize: 8 - DeviceName: xvde Ebs: VolumeType: gp2 DeleteOnTermination: true VolumeSize: 100 UserData: Fn::Base64: !Sub | <powershell> mkdir C:\bootstrap $( try {

Initialize and format the data volume

Stop-Service -Name ShellHWDetection Get-Disk | Where PartitionStyle -eq 'raw' | Initialize-Disk -PartitionStyle GPT -PassThru | New-Partition -AssignDriveLetter -UseMaximumSize | Format-Volume -FileSystem NTFS -Confirm:$false Start-Service -Name ShellHWDetection

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

$environmentName = "${TagEnvironment}" #~~~ swap when in the CloudFormation template
          $environmentName = "rpt" 
$s3bucket = "${BucketNamePrefix}-$($environmentName)" #~~~ swap when in the CloudFormation template
          $s3bucket = "edu-arizona-sls-nonprod-d2lcsr-trn"
$tasksAreDisabled = "${ScheduledTasksDisabled}" #~~~ swap when in the CloudFormation template
          $tasksAreDisabled = "true"
$rootBootstrap = "C:\bootstrap" $serverType = "TaskServer" $s3keyPowerShell = "\BootstrapScripts\"

Download PowerShell bootstrap script from S3

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

Run bootstrap script (dot-sourced)

. (Join-Path -Path $rootBootstrap -ChildPath "d2lcsrBuild$($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 instanceId

$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 "d2lcsrSMTPServer").Parameters.Value $emailSMTPUsername = (Get-SSMParameterValue -Name "d2lcsrSMTPUsername" -WithDecryption $True).Parameters.Value $emailSMTPPassword = (Get-SSMParameterValue -Name "d2lcsrSMTPPassword" -WithDecryption $True).Parameters.Value $emailTo = (Get-SSMParameterValue -Name "d2lcsrEmailTo").Parameters.Value $emailFrom = (Get-SSMParameterValue -Name "d2lcsrEmailFrom").Parameters.Value

Finish email config

$emailCred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $emailSMTPUsername, ($emailSMTPPassword | ConvertTo-SecureString -asPlainText -Force) $emailSubject = "(AWS-" + $environmentName.ToUpper() + ") " + $serverType + " deployment FAILURE - " + $awsInstanceId $emailBody = $serverType + " deployment failure on instance " + $awsInstanceId + " in environment " + $environmentName.ToUpper() + " at: " + (Get-Date).AddHours($utcOffset).ToString("G") + " (UTC-7)`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 } ) *> C:\bootstrap\bootstrap-output.txt </powershell> LaunchConfigWeb: Type: AWS::AutoScaling::LaunchConfiguration Properties: ImageId: !Ref WindowsAmiParamStore KeyName: !Ref Ec2KeyPairName InstanceType: !Ref InstanceTypeWeb InstanceMonitoring: true BlockDeviceMappings: - DeviceName: /dev/sda1 Ebs: VolumeType: gp2 DeleteOnTermination: true VolumeSize: 80 - DeviceName: xvdd Ebs: VolumeType: gp2 DeleteOnTermination: true VolumeSize: 4 IamInstanceProfile: !Ref IAMProfileWebServer SecurityGroups: - Ref: SecurityGroupForWebServers UserData: Fn::Base64: !Sub | <powershell> mkdir C:\bootstrap $( try {

Initialize and format the data volume

Stop-Service -Name ShellHWDetection Get-Disk | Where PartitionStyle -eq 'raw' | Initialize-Disk -PartitionStyle GPT -PassThru | New-Partition -AssignDriveLetter -UseMaximumSize | Format-Volume -FileSystem NTFS -Confirm:$false Start-Service -Name ShellHWDetection

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

$environmentName = "${TagEnvironment}" #~~~ swap when in the CloudFormation template
          $environmentName = "rpt" 
$s3bucket = "${BucketNamePrefix}-$($environmentName)" #~~~ swap when in the CloudFormation template
          $s3bucket = "edu-arizona-sls-nonprod-d2lcsr-trn"

Get the instanceID from the instance metadata

$awsInstanceId = Invoke-RestMethod -Uri "http://169.254.169.254/latest/meta-data/instance-id" $rootBootstrap = "C:\bootstrap" $serverType = "WebServer" $s3keyPowerShell = "\BootstrapScripts\"

Download PowerShell bootstrap script from S3

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

Run bootstrap script (dot-sourced)

. (Join-Path -Path $rootBootstrap -ChildPath "d2lcsrBuild$($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")

Retrieve email configuration from Systems Manager Parameter Store

$emailSMTPServer = (Get-SSMParameterValue -Name "d2lcsrSMTPServer").Parameters.Value $emailSMTPUsername = (Get-SSMParameterValue -Name "d2lcsrSMTPUsername" -WithDecryption $True).Parameters.Value $emailSMTPPassword = (Get-SSMParameterValue -Name "d2lcsrSMTPPassword" -WithDecryption $True).Parameters.Value $emailTo = (Get-SSMParameterValue -Name "d2lcsrEmailTo").Parameters.Value $emailFrom = (Get-SSMParameterValue -Name "d2lcsrEmailFrom").Parameters.Value

Finish email config

$emailCred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $emailSMTPUsername, ($emailSMTPPassword | ConvertTo-SecureString -asPlainText -Force) $emailSubject = "(AWS-" + $environmentName.ToUpper() + ") " + $serverType + " deployment FAILURE - " + $awsInstanceId $emailBody = $serverType + " deployment failure on instance " + $awsInstanceId + " in environment " + $environmentName.ToUpper() + " at: " + (Get-Date).AddHours($utcOffset).ToString("G") + " (UTC-7)`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 } ) *> C:\bootstrap\bootstrap-output.txt </powershell>

Scaling Policies

PolicyScaleUpWeb: Type: AWS::AutoScaling::ScalingPolicy Properties: AdjustmentType: ChangeInCapacity AutoScalingGroupName: !Ref WebServerAutoScalingGroup EstimatedInstanceWarmup: 1800 MetricAggregationType: Average PolicyType: StepScaling StepAdjustments: - MetricIntervalLowerBound: 0 ScalingAdjustment: !If [ IsPRD, 2, 1 ] PolicyScaleDownWeb: Type: AWS::AutoScaling::ScalingPolicy Properties: AdjustmentType: ChangeInCapacity AutoScalingGroupName: !Ref WebServerAutoScalingGroup MetricAggregationType: Average PolicyType: StepScaling StepAdjustments: - MetricIntervalLowerBound: 0 ScalingAdjustment: !If [ IsPRD, -2, -1 ]

CloudWatch Alarms

AlarmHighCPU: Type: AWS::CloudWatch::Alarm Properties: EvaluationPeriods: 3 Statistic: Average Threshold: 80 AlarmDescription: Alarm if CPU too high or metric disappears indicating instance is down Period: 300 AlarmActions: - Ref: PolicyScaleUpWeb Namespace: AWS/EC2 Dimensions: - Name: AutoScalingGroupName Value: !Ref WebServerAutoScalingGroup ComparisonOperator: GreaterThanOrEqualToThreshold MetricName: CPUUtilization AlarmLowCPU: Type: AWS::CloudWatch::Alarm Properties: EvaluationPeriods: 3 Statistic: Average Threshold: 30 AlarmDescription: Alarm if CPU sufficiently low Period: 300 AlarmActions: - Ref: PolicyScaleDownWeb Namespace: AWS/EC2 Dimensions: - Name: AutoScalingGroupName Value: !Ref WebServerAutoScalingGroup ComparisonOperator: LessThanOrEqualToThreshold MetricName: CPUUtilization

Route53

Route53 Record Set Group

Route53RecordSetGroup: Type: AWS::Route53::RecordSetGroup Properties: HostedZoneName: Fn::ImportValue: !Sub ${S3StackName}-hostedzone-name Comment: !Sub D2L Course Site Request (${TagEnvironment}) - aliases for web apps to ELB & CloudFront RecordSets: - Name: Fn::ImportValue: !Sub ${S3StackName}-fqdn-for-csr Type: A AliasTarget: HostedZoneId: !GetAtt WebServerLoadBalancer.CanonicalHostedZoneID DNSName: !GetAtt WebServerLoadBalancer.DNSName EvaluateTargetHealth: true Failover: PRIMARY SetIdentifier: !Sub d2lcsr-${TagEnvironment}-csr-primary - Name: Fn::ImportValue: !Sub ${S3StackName}-fqdn-for-csr Type: A AliasTarget: HostedZoneId: Z2FDTNDATAQYW2 DNSName: Fn::ImportValue: !Sub ${CloudFrontStackName}-cloudfront-maint-fqdn Failover: SECONDARY SetIdentifier: !Sub d2lcsr-${TagEnvironment}-csr-secondary - Name: Fn::ImportValue: !Sub ${S3StackName}-fqdn-for-grade Type: A AliasTarget: HostedZoneId: !GetAtt WebServerLoadBalancer.CanonicalHostedZoneID DNSName: !GetAtt WebServerLoadBalancer.DNSName - Name: Fn::ImportValue: !Sub ${S3StackName}-fqdn-for-course Type: A AliasTarget: HostedZoneId: !GetAtt WebServerLoadBalancer.CanonicalHostedZoneID DNSName: !GetAtt WebServerLoadBalancer.DNSName - Name: Fn::ImportValue: !Sub ${S3StackName}-fqdn-for-valencetest Type: A AliasTarget: HostedZoneId: !GetAtt WebServerLoadBalancer.CanonicalHostedZoneID DNSName: !GetAtt WebServerLoadBalancer.DNSName

Placeholder record set for task server alias (actual value gets set as part of task server user data script)

- Name: Fn::ImportValue: !Sub ${S3StackName}-fqdn-for-task Type: A TTL: 300 ResourceRecords: - 127.0.0.1 Route53AliasWebA: Type: AWS::Route53::RecordSet Properties: HostedZoneName: Fn::ImportValue: !Sub ${S3StackName}-hostedzone-name Comment: !Sub D2LCSR (${TagEnvironment}) alias for web server in AZ A Name: Fn::ImportValue: !Sub ${S3StackName}-fqdn-for-web-a Type: A TTL: 300 ResourceRecords: - 127.0.0.1 Route53AliasWebB: Type: AWS::Route53::RecordSet Properties: HostedZoneName: Fn::ImportValue: !Sub ${S3StackName}-hostedzone-name Comment: !Sub D2LCSR (${TagEnvironment}) alias for web server in AZ B Name: Fn::ImportValue: !Sub ${S3StackName}-fqdn-for-web-b Type: A TTL: 300 ResourceRecords: - 127.0.0.1

SNS

SNS Topic

SNSTopic: Type: AWS::SNS::Topic Properties: TopicName: !Sub d2lcsr-web-${TagEnvironment} DisplayName: !Sub AWS D2L CSR (${TagEnvironment}) Subscription: - Endpoint: !Ref EmailForSNSSubscription Protocol: email

IAM

IAM Roles

IAMRoleWebServer: Type: AWS::IAM::Role Properties: RoleName: !Sub d2lcsr-${TagEnvironment}-role-web-server AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - ec2.amazonaws.com Action: - sts:AssumeRole Path: / IAMRoleTaskServer: Type: AWS::IAM::Role Properties: RoleName: !Sub d2lcsr-${TagEnvironment}-role-task-server AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - ec2.amazonaws.com Action: - sts:AssumeRole Path: /

IAM Instance Profiles

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

IAM Policies

IAMPolicyS3BucketTaskServer: Type: AWS::IAM::Policy Properties: PolicyName: !Sub d2lcsr-${TagEnvironment}-policy-s3bucket-task PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - s3:ListBucket - s3:GetBucketLocation Resource: - Fn::ImportValue: !Sub ${S3StackName}-bucket-app-arn - Effect: Allow Action: - s3:GetObjectMetaData - s3:GetObject Resource: - Fn::ImportValue: !Sub ${S3StackName}-bucket-app-arn-wildcard - Effect: Allow Action: - s3:ListBucket - s3:GetBucketLocation Resource: - Fn::ImportValue: !Sub ${S3StackName}-bucket-tableexport-arn - Effect: Allow Action: - s3:GetObjectMetaData - s3:GetObject - s3:PutObject Resource: - Fn::ImportValue: !Sub ${S3StackName}-bucket-tableexport-arn-wildcard Roles: - !Ref IAMRoleTaskServer IAMPolicyS3BucketWebServer: Type: AWS::IAM::Policy Properties: PolicyName: !Sub d2lcsr-${TagEnvironment}-policy-s3bucket-web PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - s3:ListBucket - s3:GetBucketLocation Resource: - Fn::ImportValue: !Sub ${S3StackName}-bucket-app-arn - Effect: Allow Action: - s3:GetObjectMetaData - s3:GetObject Resource: - Fn::ImportValue: !Sub ${S3StackName}-bucket-app-arn-wildcard Roles: - !Ref IAMRoleWebServer IAMPolicyRoute53: Type: AWS::IAM::Policy Properties: PolicyName: !Sub d2lcsr-${TagEnvironment}-policy-route53 PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - cloudformation:DescribeStacks - route53:ChangeResourceRecordSets - route53:ListHostedZones Resource: - "*" Roles: - !Ref IAMRoleTaskServer - !Ref IAMRoleWebServer IAMPolicyCloudWatchLogs: Type: AWS::IAM::Policy Properties: PolicyName: !Sub d2lcsr-${TagEnvironment}-policy-cloudwatch-logs PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - cloudwatch:PutMetricData Resource: - "*" - Effect: Allow Action: - logs:CreateLogGroup - logs:CreateLogStream - logs:DescribeLogGroups - logs:DescribeLogStreams - logs:PutLogEvents Resource: - "*" Roles: - !Ref IAMRoleWebServer - !Ref IAMRoleTaskServer IAMPolicySSM: Type: AWS::IAM::Policy Properties: PolicyName: !Sub d2lcsr-${TagEnvironment}-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/d2lcsr*" Roles: - !Ref IAMRoleWebServer - !Ref IAMRoleTaskServer IAMPolicyTagEC2: Type: AWS::IAM::Policy Properties: PolicyName: !Sub d2lcsr-${TagEnvironment}-policy-ec2-tagging PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - ec2:CreateTags - ec2:DescribeTags Resource: - "*" Roles: - !Ref IAMRoleWebServer - !Ref IAMRoleTaskServer IAMPolicyDecryptKMS: Type: AWS::IAM::Policy Properties: PolicyName: !Sub d2lcsr-${TagEnvironment}-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 IAMRoleTaskServer IAMPolicyS3BucketCatnetExportTaskServer: Type: AWS::IAM::Policy Properties: PolicyName: !Sub d2lcsr-${TagEnvironment}-policy-s3bucket-catnet-export-task PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - s3:ListBucket - s3:GetBucketLocation - s3:PutObject* - s3:DeleteObject Resource: - arn:aws:s3:::edu-arizona-catnet-training-d2l-export - arn:aws:s3:::edu-arizona-catnet-training-d2l-export/* Roles: - !Ref IAMRoleTaskServer

Outputs

Outputs are values resulting from the CloudFormation stack that can be: 1) Viewed in the AWS cosole under the CloudFormation service. 2) Marked as export to be imported into another stack allowing cross-stack references.

Outputs: FQDNforCSR: Description: FQDN for the CSR application (hosted zone) Value: Fn::ImportValue: !Sub ${S3StackName}-fqdn-for-csr Export: Name: !Sub ${AWS::StackName}-fqdn-for-csr FQDNforGradeFeed: Description: FQDN for the Grade Feed application (hosted zone) Value: Fn::ImportValue: !Sub ${S3StackName}-fqdn-for-grade Export: Name: !Sub ${AWS::StackName}-fqdn-for-grade FQDNforCourseFeed: Description: FQDN for the Course Feed application (hosted zone) Value: Fn::ImportValue: !Sub ${S3StackName}-fqdn-for-course Export: Name: !Sub ${AWS::StackName}-fqdn-for-course FQDNforValenceTest: Description: FQDN for the Valence Test Tool application (hosted zone) Value: Fn::ImportValue: !Sub ${S3StackName}-fqdn-for-valencetest Export: Name: !Sub ${AWS::StackName}-fqdn-for-valencetest