AWSTemplateFormatVersion: 2010-09-09 Description: >- PortalServer, in ASG behind ALB (qs-1qtp7fd38). Parameters: PortalServerInstanceType: Description: Portal Server EC2 instance type Type: String AllowedValues: - t3.micro - t3.small - t3.medium - t3.large - m5.large - m5.xlarge - m5.2xlarge - m5.4xlarge - m5.8xlarge - c5.large - c5.xlarge - c5.2xlarge - c5.4xlarge - c5.9xlarge - r5.large - r5.xlarge - r5.2xlarge - r5.4xlarge - r5.8xlarge ConstraintDescription: must be a valid EC2 instance type. KeyPairName: Type: 'AWS::EC2::KeyPair::KeyName' ConstraintDescription: Name of an existing EC2 KeyPair. RemoteAccessCIDR: AllowedPattern: >- ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$ ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/x Description: Allowed CIDR block for ssh PortalServer access Type: String 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, and hyphens (-). It cannot start or end with a hyphen (-). 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 (/). 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 PrivateSubnet1ID: Description: Private Subnet Id 1 Type: 'AWS::EC2::Subnet::Id' PrivateSubnet2ID: Description: Private Subnet Id 2 Type: 'AWS::EC2::Subnet::Id' PublicSubnet1ID: Description: Public Subnet Id 1 Type: 'AWS::EC2::Subnet::Id' PublicSubnet2ID: Description: Public Subnet Id 2 Type: 'AWS::EC2::Subnet::Id' VPCID: Description: 'ID of the VPC (e.g., vpc-0343606e)' Type: 'AWS::EC2::VPC::Id' VPCCIDR: AllowedPattern: >- ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$ ConstraintDescription: Must be a valid IP range in x.x.x.x/x notation Description: The CIDR IP range of VPC Type: String RedisHost: Description: Hostname of your ElastiCache Redis cluster (e.g. dngcluster.abc1.0001.usw2.cache.amazonaws.com) Type: String RedisPort: Description: Redis traffic port number. Type: String RedisAuth: Description: Redis AUTH token Type: String LatestAmiId: Type: 'AWS::SSM::Parameter::Value' Default: '/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2' PortalServerName: #Default: 'LinuxBastion' Description: The value used for the name tag of the portal server Type: String DomainName: Type: String Description: Fully qualified domain name for the Duo Network Gateway load balancer. If you don't provide a value for ACMSSLCertificateArn, use the HostedZoneID. MaxLength: 128 Default: "" HostedZoneID: Type: String Description: Route 53-hosted zone ID of the domain name. If you don't provide an ACMSSLCertificateArn value, the Quick Start creates the ACM certificate for you using HostedZoneID in conjunction with DomainName. Default: "" ACMSSLCertificateArn: Description: Amazon Resource Name (ARN) of the load balancer's SSL certificate. If you don't provide values for DomainName and HostedZoneID, provide a value for ACMSSLCertificateArn. Type: String Default: '' QSS3BucketRegion: Default: 'us-east-1' Description: 'The AWS Region where the Quick Start S3 bucket (QSS3BucketName) is hosted. When using your own bucket, you must specify this value.' Type: String #Mappings: # AWSAMIRegionMap: # AMI: # US1604HVM: ubuntu/images/hvm-ssd/ubuntu-xenial-16.04-amd64-server-20180405 # ap-northeast-1: # US1604HVM: ami-60a4b21c # ap-northeast-2: # US1604HVM: ami-633d920d # ap-south-1: # US1604HVM: ami-dba580b4 # ap-southeast-1: # US1604HVM: ami-82c9ecfe # ap-southeast-2: # US1604HVM: ami-2b12dc49 # ca-central-1: # US1604HVM: ami-9d7afcf9 # eu-central-1: # US1604HVM: ami-cd491726 # eu-west-1: # US1604HVM: ami-74e6b80d # eu-west-2: # US1604HVM: ami-506e8f37 # sa-east-1: # US1604HVM: ami-5782d43b # us-east-1: # US1604HVM: ami-6dfe5010 # us-east-2: # US1604HVM: ami-e82a1a8d # us-west-1: # US1604HVM: ami-493f2f29 # us-west-2: # US1604HVM: ami-ca89eeb2 Conditions: UsingDefaultBucket: !Equals [!Ref QSS3BucketName, 'aws-quickstart'] GovCloudCondition: !Equals - !Ref 'AWS::Region' - us-gov-west-1 CustomDns: !Not [!Equals [!Ref DomainName, '']] CreateDns: !And - !Not - !Equals - !Ref 'HostedZoneID' - '' - !Not - !Equals - !Ref 'DomainName' - '' Resources: SSMRole: Type: "AWS::IAM::Role" Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Principal: Service: ["ec2.amazonaws.com"] Action: "sts:AssumeRole" Path: "/" ManagedPolicyArns: - 'arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore' - 'arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy' InstanceProfile: Type: AWS::IAM::InstanceProfile Properties: Path: '/' Roles: - !Ref SSMRole PortalServerGroup: Type: 'AWS::AutoScaling::AutoScalingGroup' Properties: VPCZoneIdentifier: - !Ref PrivateSubnet1ID - !Ref PrivateSubnet2ID LaunchConfigurationName: !Ref PortalServerLaunchConfig MinSize: '2' MaxSize: '8' TargetGroupARNs: - !Ref ALBTargetGroup Tags: - Key: Name Value: !Ref PortalServerName PropagateAtLaunch: true CreationPolicy: ResourceSignal: Timeout: PT15M Count: 1 UpdatePolicy: AutoScalingRollingUpdate: MinInstancesInService: 1 MaxBatchSize: 1 PauseTime: PT15M WaitOnResourceSignals: true PortalServerLaunchConfig: Type: 'AWS::AutoScaling::LaunchConfiguration' Metadata: 'AWS::CloudFormation::Init': configSets: bootstrap: - export-redis-env-variables #- config-docker - install-docker - config-docker-compose - add-user - config-portal-server #config-docker: # commands: # add_gpg_key: # command: wget -O- "https://download.docker.com/linux/ubuntu/gpg" | sudo apt-key add - # add_repo: # command: sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" # update_repo: # command: sudo apt-get update export-redis-env-variables: commands: 1_export_redis_host: command: !Sub echo export REDIS_HOST=${RedisHost} >> /etc/profile.d/env.sh cwd: "~" 2_export_redis_port: command: !Sub echo export REDIS_PORT=${RedisPort} >> /etc/profile.d/env.sh cwd: "~" 3_export_redis_auth: command: !Sub echo export REDIS_AUTH=${RedisAuth} >> /etc/profile.d/env.sh cwd: "~" #4_source_env_sh: # command: source /etc/profile.d/env.sh # cwd: "~" install-docker: packages: yum: docker: [] commands: 1_enable_docker_service: command: sudo systemctl enable docker.service 2_start_docker_service: command: sudo systemctl start docker config-docker-compose: commands: 1_download-docker-compose: command: wget -O- "https://github.com/docker/compose/releases/download/1.23.1/docker-compose-$(uname -s)-$(uname -m)" > ./docker-compose 2_set-permissions: command: chmod +x ./docker-compose 3_change-docker-compose-location: command: sudo mv ./docker-compose /usr/bin/ add-user: commands: add-user-to-docker-group: command: sudo usermod -aG docker $(whoami) config-portal-server: commands: 1_download_portal_server_image: command: wget --content-disposition https://dl.duosecurity.com/network-gateway-ha-latest.yml cwd: "~" 2_install_portal_server_image: command: docker-compose -p network-gateway -f $(ls network-gateway*) up -d env: REDIS_HOST: !Sub ${RedisHost} REDIS_PORT: !Sub ${RedisPort} REDIS_AUTH: !Sub ${RedisAuth} cwd: "~" Properties: KeyName: !Ref KeyPairName ImageId: !Ref LatestAmiId #!FindInMap # - AWSAMIRegionMap # - !Ref 'AWS::Region' # - US1604HVM InstanceType: !Ref PortalServerInstanceType IamInstanceProfile: !Ref InstanceProfile SecurityGroups: - !Ref PortalServerSecurityGroup UserData: !Base64 'Fn::Sub': - > #!/bin/bash -x echo \'[Installing git]\' sudo yum install git -y #CFN Functions function cfn_fail { cfn-signal -e 1 --stack ${AWS::StackName} --region ${AWS::Region} --resource PortalServerGroup exit 1 } function cfn_success { cfn-signal -e 0 --stack ${AWS::StackName} --region ${AWS::Region} --resource PortalServerGroup exit 0 } S3URI=https://${QSS3BucketName}.${S3Region}.amazonaws.com/${QSS3KeyPrefix} echo \'[Cloning: Load QuickStart Common Utils]\' #Once Repo is public (Use quickstart-linux-utilities submodule) sudo git clone https://github.com/aws-quickstart/quickstart-linux-utilities.git source /quickstart-linux-utilities/quickstart-cfn-tools.source echo \'[Loaded: Load QuickStart Common Utils]\' echo \'[Update Operating System]\' qs_update-os || qs_err qs_bootstrap_pip || qs_err qs_aws-cfn-bootstrap || qs_err cfn-init -v --stack ${AWS::StackName} --resource PortalServerLaunchConfig --configsets bootstrap --region ${AWS::Region} || cfn_fail # Signal cfn-init (final check) [ $(qs_status) == 0 ] && cfn_success || cfn_fail - S3Region: !If - GovCloudCondition - s3-us-gov-west-1 - s3 PortalServerScaleUpPolicy: Type: 'AWS::AutoScaling::ScalingPolicy' Properties: AdjustmentType: ChangeInCapacity AutoScalingGroupName: !Ref PortalServerGroup Cooldown: '60' ScalingAdjustment: 1 PortalServerScaleDownPolicy: Type: 'AWS::AutoScaling::ScalingPolicy' Properties: AdjustmentType: ChangeInCapacity AutoScalingGroupName: !Ref PortalServerGroup Cooldown: '60' ScalingAdjustment: -1 PortalServerCPUAlarmHigh: Type: 'AWS::CloudWatch::Alarm' Properties: AlarmDescription: Scale-up if CPU > 90% for 10 minutes MetricName: CPUUtilization Namespace: AWS/EC2 Statistic: Average Period: 300 EvaluationPeriods: 2 Threshold: 90 AlarmActions: - !Ref PortalServerScaleUpPolicy Dimensions: - Name: AutoScalingGroupName Value: !Ref PortalServerGroup ComparisonOperator: GreaterThanThreshold PortalServerCPUAlarmLow: Type: 'AWS::CloudWatch::Alarm' Properties: AlarmDescription: Scale-down if CPU < 70% for 10 minutes MetricName: CPUUtilization Namespace: AWS/EC2 Statistic: Average Period: 300 EvaluationPeriods: 2 Threshold: 70 AlarmActions: - !Ref PortalServerScaleDownPolicy Dimensions: - Name: AutoScalingGroupName Value: !Ref PortalServerGroup ComparisonOperator: LessThanThreshold ApplicationLoadBalancer: Type: 'AWS::ElasticLoadBalancingV2::LoadBalancer' Properties: Subnets: - !Ref PublicSubnet1ID - !Ref PublicSubnet2ID SecurityGroups: - !Ref LoadBalancerSecurityGroup Tags: - Key: name Value: PortalServer ASG Instance ALBListener: Type: 'AWS::ElasticLoadBalancingV2::Listener' Properties: Certificates: - CertificateArn: !If [ CreateDns, !GetAtt "ACMCertificate.Outputs.ACMCertificate", !Ref ACMSSLCertificateArn ] DefaultActions: - Type: forward TargetGroupArn: !Ref ALBTargetGroup LoadBalancerArn: !Ref ApplicationLoadBalancer Port: 443 Protocol: HTTPS PortalServerListenerRedirectToHTTPS: Type: "AWS::ElasticLoadBalancingV2::Listener" Properties: DefaultActions: - Type: "redirect" RedirectConfig: Protocol: "HTTPS" Port: "443" Host: "#{host}" Path: "/#{path}" Query: "#{query}" StatusCode: "HTTP_301" LoadBalancerArn: !Ref ApplicationLoadBalancer Port: 80 Protocol: "HTTP" DNGListenerRule: Type: AWS::ElasticLoadBalancingV2::ListenerRule Properties: ListenerArn: !Ref ALBListener Priority: 1 Conditions: - Field: path-pattern Values: - "/" Actions: - Type: forward TargetGroupArn: Ref: ALBTargetGroup ALBTargetGroup: Type: 'AWS::ElasticLoadBalancingV2::TargetGroup' Properties: Port: 443 Protocol: HTTPS VpcId: !Ref VPCID Matcher: HttpCode: '200' HealthCheckIntervalSeconds: 10 HealthCheckPath: /health-check HealthCheckProtocol: HTTPS HealthCheckTimeoutSeconds: 5 HealthyThresholdCount: 2 UnhealthyThresholdCount: 5 LoadBalancerSecurityGroup: Type: 'AWS::EC2::SecurityGroup' Properties: GroupDescription: Enable HTTP and HTTPS access to the load balancer SecurityGroupIngress: - IpProtocol: tcp FromPort: 80 ToPort: 80 CidrIp: !Ref RemoteAccessCIDR - IpProtocol: tcp FromPort: 443 ToPort: 443 CidrIp: !Ref RemoteAccessCIDR VpcId: !Ref VPCID PortalServerSecurityGroup: Type: 'AWS::EC2::SecurityGroup' Properties: GroupDescription: Enable HTTP, HTTPS and SSH to the Portal Server SecurityGroupIngress: - IpProtocol: tcp FromPort: 80 ToPort: 80 CidrIp: !Ref VPCCIDR - IpProtocol: tcp FromPort: 443 ToPort: 443 CidrIp: !Ref VPCCIDR - IpProtocol: tcp FromPort: 22 ToPort: 22 CidrIp: !Ref RemoteAccessCIDR VpcId: !Ref VPCID ACMCertificate: Metadata: cfn-lint: config: ignore_checks: - W9198 Type: AWS::CloudFormation::Stack Condition: CreateDns Properties: TemplateURL: !Sub - 'https://${S3Bucket}.s3.${S3Region}.${AWS::URLSuffix}/${QSS3KeyPrefix}submodules/quickstart-aws-acm-certificate/templates/quickstart-aws-acm-certificate.template.yml' - S3Region: !If [UsingDefaultBucket, !Ref 'AWS::Region', !Ref QSS3BucketRegion] S3Bucket: !If [UsingDefaultBucket, !Sub '${QSS3BucketName}-${AWS::Region}', !Ref QSS3BucketName] Parameters: QSS3BucketName: !Ref QSS3BucketName QSS3BucketRegion: !Ref QSS3BucketRegion QSS3KeyPrefix: !Sub '${QSS3KeyPrefix}submodules/quickstart-aws-acm-certificate/' DomainName: !Ref DomainName HostedZoneID: !Ref HostedZoneID DNGDNSRecord: Condition: CreateDns Type: AWS::Route53::RecordSet Properties: Type: A Name: !Ref DomainName AliasTarget: HostedZoneId: !GetAtt ApplicationLoadBalancer.CanonicalHostedZoneID DNSName: !GetAtt ApplicationLoadBalancer.DNSName HostedZoneId: !Ref 'HostedZoneID' Outputs: DNGLoadBalancer: Value: !Sub - "https://${DNSAddress}/" - DNSAddress: !If [ CustomDns, !Ref DomainName, !GetAtt "ApplicationLoadBalancer.DNSName" ]