AWSTemplateFormatVersion: '2010-09-09' Description: >- AWS Quick Start: CloudFormation stack (nested) for creating the infrastructure for Tableau Server to run in. (qs-1t14k9mo6) Metadata: cfn-lint: config: ignore_checks: - W9002 - W9003 - W9006 AWS::CloudFormation::Interface: ParameterGroups: - Label: default: 'AWS Configuration' Parameters: - Route53HostedZone - Route53DomainName - SourceCIDR - TableauServerSubdomain - TsmSubdomain - TableauS3BucketName - VPCID - PublicSubnet1ID - PublicSubnet2ID ParameterLabels: VPCID: default: VPC ID PublicSubnet1ID: default: Public Subnet 1 ID PublicSubnet2ID: default: Public Subnet 2 ID Route53HostedZone: default: Hosted Zone Route53DomainName: default: Domain name for your hosted zone SourceCIDR: default: CIDR block for Ingress rule TableauServerSubdomain: default: Subdomain for Tableau Server's URL TsmSubdomain: default: Subdomain for TSM UI's URL TableauS3BucketName: default: Name of S3 bucket for Tableau Server files Parameters: VPCID: Description: The ID of your existing VPC for deployment. Type: AWS::EC2::VPC::Id PublicSubnet1ID: Description: ID of public subnet 1 in Availability Zone 1 for the ELB load balancer (for example, subnet-9bc642ac). Type: AWS::EC2::Subnet::Id PublicSubnet2ID: Description: ID of public subnet 2 in Availability Zone 2 for the ELB load balancer (for example, subnet-e3246d8e). Type: AWS::EC2::Subnet::Id Route53HostedZone: Description: Hosted Zone from Route53 Type: AWS::Route53::HostedZone::Id Route53DomainName: Description: Domain name for your Route53 Hosted Zone Type: String SourceCIDR: 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: The CIDR address from which you will connect to the instance Type: String TableauS3BucketName: Description: S3 bucket to store Tableau Server files Type: String TableauServerSubdomain: MaxLength: '25' Description: Subdomain for Tableau Server URL Type: String TsmSubdomain: Description: Subdomain for TSM UI URL 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 (-). Default: aws-quickstart Description: S3 bucket name for the Quick Start assets. This string 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-tableau-server/ 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 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: SecurityGroup: props: name: Tableau Security Group desc: Security Group for Tableau Server ALB: names: TableauServer: Tableau-Server Tsm: Tableau-TSM Conditions: CreateS3Bucket: !Equals - !Ref 'TableauS3BucketName' - '' UsingDefaultBucket: !Equals [!Ref QSS3BucketName, 'aws-quickstart'] Resources: SecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: !FindInMap ["SecurityGroup", "props", "desc"] SecurityGroupEgress: - CidrIp: "0.0.0.0/0" IpProtocol: "-1" SecurityGroupIngress: - CidrIp: !Ref SourceCIDR FromPort: "80" ToPort: "80" IpProtocol: "tcp" - CidrIp: !Ref SourceCIDR FromPort: "443" ToPort: "443" IpProtocol: "tcp" Tags: - Key: DeploymentName Value: !FindInMap ["SecurityGroup", "props", "name"] VpcId: !Ref VPCID SecurityGroupRule: Type: AWS::EC2::SecurityGroupIngress Properties: GroupId: !Ref SecurityGroup SourceSecurityGroupId: !Ref SecurityGroup IpProtocol: "-1" SecurityGroupRule2: Type: AWS::EC2::SecurityGroupIngress Properties: GroupId: !Ref SecurityGroup CidrIp: !Ref SourceCIDR IpProtocol: "tcp" FromPort: "3389" ToPort: "3389" Description: "RDP" TableauS3Bucket: Condition: CreateS3Bucket Type: 'AWS::S3::Bucket' TableauIAMRole: Type: AWS::IAM::Role Properties: Path: / AssumeRolePolicyDocument: Version: '2012-10-17' Statement: Effect: Allow Principal: Service: ec2.amazonaws.com Action: sts:AssumeRole ManagedPolicyArns: - !Sub 'arn:${AWS::Partition}:iam::aws:policy/AmazonSSMManagedInstanceCore' - !Sub 'arn:${AWS::Partition}:iam::aws:policy/AmazonSSMDirectoryServiceAccess' - !Sub 'arn:${AWS::Partition}:iam::aws:policy/CloudWatchAgentServerPolicy' Policies: - PolicyDocument: Version: '2012-10-17' Statement: - Action: - s3:GetObject - s3:PutObject - s3:ListBucket Resource: - Fn::If: - CreateS3Bucket - !Sub 'arn:${AWS::Partition}:s3:::${TableauS3Bucket}/*' - !Sub 'arn:${AWS::Partition}:s3:::${TableauS3BucketName}/*' - Fn::If: - CreateS3Bucket - !Sub 'arn:${AWS::Partition}:s3:::${TableauS3Bucket}' - !Sub 'arn:${AWS::Partition}:s3:::${TableauS3BucketName}' Effect: Allow PolicyName: back-to-s3 - PolicyDocument: Statement: - Action: - 's3:GetObject' Effect: Allow Resource: - !Sub - arn:${AWS::Partition}:s3:::${S3Bucket} - S3Bucket: !If [ UsingDefaultBucket, !Sub '${QSS3BucketName}-${AWS::Region}', !Ref QSS3BucketName ] - !Sub - arn:${AWS::Partition}:s3:::${S3Bucket}/${QSS3KeyPrefix}* - S3Bucket: !If [ UsingDefaultBucket, !Sub '${QSS3BucketName}-${AWS::Region}', !Ref QSS3BucketName ] Version: 2012-10-17 PolicyName: aws-quick-start-s3-policy - PolicyDocument: Version: '2012-10-17' Statement: - Action: - ec2:CreateTags - ec2:DescribeInstanceAttribute Effect: Allow Resource: - !Sub 'arn:${AWS::Partition}:ec2:${AWS::Region}:${AWS::AccountId}:instance/*' - !Sub 'arn:${AWS::Partition}:ec2:${AWS::Region}:${AWS::AccountId}:volume/*' PolicyName: ec2-tagging - PolicyDocument: Version: '2012-10-17' Statement: - Action: elasticloadbalancing:DescribeTargetHealth Effect: Allow Resource: '*' PolicyName: elb-query-targets - PolicyDocument: Version: '2012-10-17' Statement: - Action: - elasticloadbalancing:RegisterTargets - elasticloadbalancing:DeregisterTargets Effect: Allow Resource: - Fn::Sub: - 'arn:${AWS::Partition}:elasticloadbalancing:${AWS::Region}:${AWS::AccountId}:targetgroup/${tgName}*/*' - tgName: !Ref TableauServerSubdomain - Fn::Sub: - 'arn:${AWS::Partition}:elasticloadbalancing:${AWS::Region}:${AWS::AccountId}:targetgroup/${tgName}*/*' - tgName: !Sub '${TableauServerSubdomain}-tsm' PolicyName: elb-register-targets - PolicyDocument: Version: '2012-10-17' Statement: - Action: ec2:StopInstances Effect: Allow Resource: !Sub 'arn:${AWS::Partition}:ec2:${AWS::Region}:${AWS::AccountId}:instance/*' PolicyName: ec2-stop TableauServerInstanceProfile: Type: AWS::IAM::InstanceProfile Properties: Path: / Roles: - !Ref 'TableauIAMRole' SslCertificate: Type: AWS::CertificateManager::Certificate Properties: DomainName: !Ref Route53DomainName SubjectAlternativeNames: - !Sub '*.${Route53DomainName}' ValidationMethod: DNS DomainValidationOptions: - DomainName: !Ref Route53DomainName HostedZoneId: !Ref Route53HostedZone TableauServerRoute53: Type: AWS::Route53::RecordSet Properties: Type: A AliasTarget: HostedZoneId: !GetAtt TableauServerAlb.CanonicalHostedZoneID DNSName: !GetAtt TableauServerAlb.DNSName HostedZoneId: !Ref Route53HostedZone Name: Fn::Sub: - "${p1}.${Route53DomainName}" - p1: !Ref TableauServerSubdomain TableauServerAlb: Type: AWS::ElasticLoadBalancingV2::LoadBalancer Properties: Scheme: "internet-facing" Type: "application" Tags: - Key: DeploymentName Value: Fn::Sub: - "${p1}-LB" - p1: !FindInMap ["ALB", "names", "TableauServer"] SecurityGroups: - !Ref SecurityGroup Subnets: - !Ref PublicSubnet1ID - !Ref PublicSubnet2ID TableauServerTG: Type: "AWS::ElasticLoadBalancingV2::TargetGroup" Properties: Port: 80 Protocol: "HTTP" TargetType: "instance" VpcId: !Ref VPCID Name: !Sub "${TableauServerSubdomain}-TG" Tags: - Key: DeploymentName Value: Fn::Sub: - "${p1}-TG" - p1: !FindInMap ["ALB", "names", "TableauServer"] TargetGroupAttributes: - Key: "stickiness.enabled" Value: "true" - Key: "stickiness.type" Value: "lb_cookie" TableauServerListenerHttps: Type: "AWS::ElasticLoadBalancingV2::Listener" Properties: LoadBalancerArn: !Ref TableauServerAlb Port: 443 Protocol: "HTTPS" SslPolicy: "ELBSecurityPolicy-2016-08" Certificates: - CertificateArn: !Ref SslCertificate DefaultActions: - Order: 1 TargetGroupArn: !Ref TableauServerTG Type: "forward" TableauServerListenerHttp: Type: AWS::ElasticLoadBalancingV2::Listener Properties: DefaultActions: - RedirectConfig: Host: "#{host}" Path: "/#{path}" Port: 443 Protocol: "HTTPS" Query: "#{query}" StatusCode: HTTP_301 Type: redirect LoadBalancerArn: !Ref 'TableauServerAlb' Port: 80 Protocol: HTTP TsmRoute53: Type: AWS::Route53::RecordSet Properties: Type: A AliasTarget: HostedZoneId: !GetAtt TsmAlb.CanonicalHostedZoneID DNSName: !GetAtt TsmAlb.DNSName HostedZoneId: !Ref Route53HostedZone Name: Fn::Sub: - "${p1}.${Route53DomainName}" - p1: !Ref TsmSubdomain TsmAlb: Type: AWS::ElasticLoadBalancingV2::LoadBalancer Properties: Scheme: "internet-facing" Type: "application" Tags: - Key: DeploymentName Value: Fn::Sub: - "${p1}-LB" - p1: !FindInMap ["ALB", "names", "Tsm"] SecurityGroups: - !Ref SecurityGroup Subnets: - !Ref PublicSubnet1ID - !Ref PublicSubnet2ID TsmTG: Type: "AWS::ElasticLoadBalancingV2::TargetGroup" Properties: Port: 8850 Protocol: "HTTPS" TargetType: "instance" VpcId: !Ref VPCID Name: !Sub "${TableauServerSubdomain}-tsm-TG" Tags: - Key: DeploymentName Value: Fn::Sub: - "${p1}-TG" - p1: !FindInMap ["ALB", "names", "Tsm"] TargetGroupAttributes: - Key: "stickiness.enabled" Value: "true" - Key: "stickiness.type" Value: "lb_cookie" TsmListenerHttps: Type: "AWS::ElasticLoadBalancingV2::Listener" Properties: LoadBalancerArn: !Ref TsmAlb Port: 443 Protocol: "HTTPS" SslPolicy: "ELBSecurityPolicy-2016-08" Certificates: - CertificateArn: !Ref SslCertificate DefaultActions: - Order: 1 TargetGroupArn: !Ref TsmTG Type: "forward" TsmListenerHttp: Type: AWS::ElasticLoadBalancingV2::Listener Properties: DefaultActions: - RedirectConfig: Host: "#{host}" Path: "/#{path}" Port: 443 Protocol: "HTTPS" Query: "#{query}" StatusCode: HTTP_301 Type: redirect LoadBalancerArn: !Ref 'TsmAlb' Port: 80 Protocol: HTTP TableauServerCloudwatch: Type: AWS::Logs::LogGroup Properties: RetentionInDays: 90 Outputs: TableauServerUrl: Description: URL of your Tableau Server Value: Fn::Sub: - "${p1}.${Route53DomainName}" - p1: !Ref TableauServerSubdomain SecurityGroup: Description: Security Group ARN Value: !Ref SecurityGroup TableauIAMRole: Description: IAM role Value: !Ref TableauIAMRole IamInstanceProfile: Description: IAM Instance Profile Value: !Ref TableauServerInstanceProfile PublicSubnetId: Description: Subnet ID Value: !Ref PublicSubnet1ID TableauServerTargetGroupArn: Description: Tableau Server ALB TG Value: !Ref TableauServerTG TsmTargetGroupArn: Description: TSM UI ALB TG Value: !Ref TsmTG TableauServerLogGroup: Description: Cloudwatch log group name for Tableau Server logs Value: !Ref TableauServerCloudwatch TableauS3Bucket: Description: S3 bucket to store Tableau Server files Value: Fn::If: - CreateS3Bucket - !Ref TableauS3Bucket - !Ref TableauS3BucketName