AWSTemplateFormatVersion: 2010-09-09
Description: LinuxBastion+VPC Jul,30,2020 (qs-1qup6ra99) (Please do not remove)
Parameters:
  LogicalId :
    Description : Logical Id of the MODULE
    Type: String
  BastionAMIOS:
    AllowedValues:
      - Amazon-Linux2-HVM
      - CentOS-7-HVM
      - Ubuntu-Server-20.04-LTS-HVM
      - SUSE-SLES-15-HVM
    Default: Amazon-Linux2-HVM
    Description: The Linux distribution for the AMI to be used for the bastion instances.
    Type: String
  BastionHostName:
    Default: 'LinuxBastion'
    Description: The value used for the name tag of the bastion host
    Type: String
  BastionBanner:
    Default: ""
    Description: Banner text to display upon login.
    Type: String
  BastionTenancy:
    Description: 'VPC tenancy to launch the bastion in. Options: ''dedicated'' or ''default'''
    Type: String
    Default: default
    AllowedValues:
      - dedicated
      - default
  BastionInstanceType:
    AllowedValues:
      - t2.nano
      - t2.micro
      - t2.small
      - t2.medium
      - t2.large
      - t3.micro
      - t3.small
      - t3.medium
      - t3.large
      - t3.xlarge
      - t3.2xlarge
      - m4.large
      - m4.xlarge
      - m4.2xlarge
      - m4.4xlarge
    Default: t2.micro
    Description: Amazon EC2 instance type for the bastion instances.
    Type: String
  EnableBanner:
    AllowedValues:
      - 'true'
      - 'false'
    Default: 'false'
    Description: To include a banner to be displayed when connecting via SSH to the
      bastion, choose true.
    Type: String
  EnableTCPForwarding:
    Type: String
    Description: To enable TCP forwarding, choose true.
    Default: 'false'
    AllowedValues:
      - 'true'
      - 'false'
  EnableX11Forwarding:
    Type: String
    Description: To enable X11 forwarding, choose true.
    Default: 'false'
    AllowedValues:
      - 'true'
      - 'false'
  KeyPairName:
    Description: Name of an existing public/private key pair. If you do not have one in this AWS Region,
      please create it before continuing.
    Type: 'AWS::EC2::KeyPair::KeyName'
  NumBastionHosts:
    AllowedValues:
      - '1'
      - '2'
      - '3'
      - '4'
    Default: '1'
    Description: The number of bastion hosts to create. The maximum number is four.
    Type: String
  PublicSubnet1Id:
    Description: ID of the public subnet in Availability Zone 1 of your existing VPC (e.g., subnet-z0376dab).
    Type: AWS::EC2::Subnet::Id
  PublicSubnet2Id:
    Description: ID of the public subnet in Availability Zone 1 of your existing VPC (e.g., subnet-z0376dab).
    Type: AWS::EC2::Subnet::Id
  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. Quick Start bucket name can
      include numbers, lowercase letters, uppercase letters, and hyphens (-). It
      cannot start or end with a hyphen (-).
    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
  QSS3KeyPrefix:
    AllowedPattern: '^([0-9a-zA-Z-.]+/)*$'
    ConstraintDescription: Quick Start key prefix can include numbers, lowercase letters, uppercase
      letters, hyphens (-), dots (.) and forward slash (/). The prefix should
      end with a forward slash (/).
    Default: quickstart-linux-bastion/
    Description: S3 key prefix for the Quick Start assets. Quick Start key prefix can
      include numbers, lowercase letters, uppercase letters, hyphens (-), dots
      (.) and forward slash (/) and it should end with a forward slash (/).
    Type: String
  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 external SSH access to the bastions.
    Type: String
  VPCID:
    Description: 'ID of the VPC (e.g., vpc-0343606e).'
    Type: 'AWS::EC2::VPC::Id'
  AlternativeInitializationScript:
    AllowedPattern: ^http.*|^$
    ConstraintDescription: URL must begin with http
    Description: An alternative initialization script to run during setup.
    Default: ''
    Type: String
  OSImageOverride:
    Description: The Region-specific image to use for the instance.
    Type: String
    Default: ''
  EnvironmentVariables:
    Description: A comma-separated list of environment variables for use in
      bootstrapping. Variables must be in the format KEY=VALUE. VALUE cannot
      contain commas.
    Type: String
    Default: ''
  RootVolumeSize:
    Description: The size in GB for the root EBS volume.
    Type: Number
    Default: '10'
Rules:
  SubnetsInVPC:
    Assertions:
      - Assert:
          'Fn::EachMemberIn':
            - 'Fn::ValueOfAll':
                - 'AWS::EC2::Subnet::Id'
                - VpcId
            - 'Fn::RefAll': 'AWS::EC2::VPC::Id'
        AssertDescription: All subnets must exist in the VPC
Mappings:
  AWSAMIRegionMap:
    ap-northeast-1:
      AMZNLINUX2: ami-0cc75a8978fbbc969
      US2004HVM: ami-0461b11e2fad8c14a
      CENTOS7HVM: ami-06a46da680048c8ae
      SLES15HVM: ami-056ac8ad44e6a7e1f
    ap-northeast-2:
      AMZNLINUX2: ami-0bd7691bf6470fe9c
      US2004HVM: ami-0dbad3c7f731477cb
      CENTOS7HVM: ami-06e83aceba2cb0907
      SLES15HVM: ami-0f81fff879bafe6b8
    ap-south-1:
      AMZNLINUX2: ami-0ebc1ac48dfd14136
      US2004HVM: ami-0ebd654017556e025
      CENTOS7HVM: ami-026f33d38b6410e30
      SLES15HVM: ami-01be89269d32f2a16
    ap-southeast-1:
      AMZNLINUX2: ami-0cd31be676780afa7
      US2004HVM: ami-0ba1d1f3433cd4c68
      CENTOS7HVM: ami-07f65177cb990d65b
      SLES15HVM: ami-070356c21596ddc67
    ap-southeast-2:
      AMZNLINUX2: ami-0ded330691a314693
      US2004HVM: ami-02be36619a83e9a16
      CENTOS7HVM: ami-0b2045146eb00b617
      SLES15HVM: ami-0c4245381c67efb39
    ca-central-1:
      AMZNLINUX2: ami-013d1df4bcea6ba95
      US2004HVM: ami-071c33c681c9d4a00
      CENTOS7HVM: ami-04a25c39dc7a8aebb
      SLES15HVM: ami-0c97d9b588207dad6
    eu-central-1:
      AMZNLINUX2: ami-0c115dbd34c69a004
      US2004HVM: ami-0c2b1c303a2e4cb49
      CENTOS7HVM: ami-0e8286b71b81c3cc1
      SLES15HVM: ami-05dfd265ea534a3e9
    me-south-1:
      AMZNLINUX2: ami-01f41d49c363da2ad
      US2004HVM: ami-07f9fe3f7a8c82448
      CENTOS7HVM: ami-011c71a894b10f35b
      SLES15HVM: ami-0252c6d3a59c7473b
    ap-east-1:
      AMZNLINUX2: ami-47317236
      US2004HVM: ami-545b1825
      CENTOS7HVM: ami-0e5c29e6c87a9644f
      SLES15HVM: ami-0ad6e15bcbb2dbe38
    eu-north-1:
      AMZNLINUX2: ami-039609244d2810a6b
      US2004HVM: ami-08baf9e3c347b7092
      CENTOS7HVM: ami-05788af9005ef9a93
      SLES15HVM: ami-0741fa1a008af40ad
    eu-west-1:
      AMZNLINUX2: ami-07d9160fa81ccffb5
      US2004HVM: ami-0f1d11c92a9467c07
      CENTOS7HVM: ami-0b850cf02cc00fdc8
      SLES15HVM: ami-0a58a1b152ba55f1d
    eu-west-2:
      AMZNLINUX2: ami-0a13d44dccf1f5cf6
      US2004HVM: ami-082335b69bcfdb15b
      CENTOS7HVM: ami-09e5afc68eed60ef4
      SLES15HVM: ami-01497522185aaa4ee
    eu-west-3:
      AMZNLINUX2: ami-093fa4c538885becf
      US2004HVM: ami-00f6fb16625871821
      CENTOS7HVM: ami-0cb72d2e599cffbf9
      SLES15HVM: ami-0f238bd4c6fdbefb0
    sa-east-1:
      AMZNLINUX2: ami-018ccfb6b4745882a
      US2004HVM: ami-083aa2af86ff2bd11
      CENTOS7HVM: ami-0b30f38d939dd4b54
      SLES15HVM: ami-0772af912976aa692
    us-east-1:
      AMZNLINUX2: ami-02354e95b39ca8dec
      US2004HVM: ami-0758470213bdd23b1
      CENTOS7HVM: ami-0affd4508a5d2481b
      SLES15HVM: ami-0b1764f3d7d2e2316
    us-gov-west-1:
      AMZNLINUX2: ami-74c4f215
      SLES15HVM: ami-57c0ba36
    us-gov-east-1:
      AMZNLINUX2: ami-30e00c41
      SLES15HVM: ami-05e4bedfad53425e9
    us-east-2:
      AMZNLINUX2: ami-07c8bc5c1ce9598c3
      US2004HVM: ami-07fb7bd53bacdfc16
      CENTOS7HVM: ami-01e36b7901e884a10
      SLES15HVM: ami-05ea824317ffc0c20
    us-west-1:
      AMZNLINUX2: ami-05655c267c89566dd
      US2004HVM: ami-0cd230f950c3de5d8
      CENTOS7HVM: ami-098f55b4287a885ba
      SLES15HVM: ami-00e34a7624e5a7107
    us-west-2:
      AMZNLINUX2: ami-0873b46c45c11058d
      US2004HVM: ami-056cb9ae6e2df09e8
      CENTOS7HVM: ami-0bc06212a56393ee1
      SLES15HVM: ami-0f1e3b3fb0fec0361
    cn-north-1:
      AMZNLINUX2: ami-010e92a33d9d1fc40
      CENTOS7HVM: ami-0e02aaefeb74c3373
      SLES15HVM: ami-021392849b6221a81
    cn-northwest-1:
      AMZNLINUX2: ami-0959f8e18a2aac0fb
      CENTOS7HVM: ami-07183a7702633260b
      SLES15HVM: ami-00e1de3ee6d0d28ea
  LinuxAMINameMap:
    Amazon-Linux2-HVM:
      Code: AMZNLINUX2
      OS: Amazon
    CentOS-7-HVM:
      Code: CENTOS7HVM
      OS: CentOS
    Ubuntu-Server-18.04-LTS-HVM:
      Code: US1804HVM
      OS: Ubuntu
    Ubuntu-Server-20.04-LTS-HVM:
      Code: US2004HVM
      OS: Ubuntu
    SUSE-SLES-15-HVM:
      Code: SLES15HVM
      OS: SLES
Conditions:
  2BastionCondition: !Or
    - !Equals
      - !Ref NumBastionHosts
      - '2'
    - !Condition 3BastionCondition
    - !Condition 4BastionCondition
  3BastionCondition: !Or
    - !Equals
      - !Ref NumBastionHosts
      - '3'
    - !Condition 4BastionCondition
  4BastionCondition: !Equals
    - !Ref NumBastionHosts
    - '4'
  UseAlternativeInitialization: !Not
    - !Equals
      - !Ref AlternativeInitializationScript
      - ''
  UseOSImageOverride: !Not
    - !Equals
      - !Ref OSImageOverride
      - ''
  UsingDefaultBucket: !Equals
    - !Ref QSS3BucketName
    - 'aws-quickstart'
  DefaultBanner: !Equals [!Ref BastionBanner, ""]
Resources:
  BastionMainLogGroup:
    Type: 'AWS::Logs::LogGroup'
  SSHMetricFilter:
    Type: 'AWS::Logs::MetricFilter'
    Properties:
      LogGroupName: !Ref BastionMainLogGroup
      FilterPattern: ON FROM USER PWD
      MetricTransformations:
        - MetricName: SSHCommandCount
          MetricValue: '1'
          MetricNamespace: !Sub "AWSQuickStart/${AWS::StackName}"
  BastionHostRole:
    Type: 'AWS::IAM::Role'
    Properties:
      Path: /
      AssumeRolePolicyDocument:
        Statement:
          - Action:
              - 'sts:AssumeRole'
            Principal:
              Service:
                - !Sub 'ec2.${AWS::URLSuffix}'
            Effect: Allow
        Version: 2012-10-17
      ManagedPolicyArns:
        - !Sub 'arn:${AWS::Partition}:iam::aws:policy/AmazonSSMManagedInstanceCore'
        - !Sub 'arn:${AWS::Partition}:iam::aws:policy/CloudWatchAgentServerPolicy'
  BastionHostPolicy:
    Type: 'AWS::IAM::Policy'
    Properties:
      PolicyName: BastionPolicy
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Action:
              - 's3:GetObject'
            Resource: !Sub
              - arn:${AWS::Partition}:s3:::${S3Bucket}/${QSS3KeyPrefix}*
              - S3Bucket: !If [UsingDefaultBucket, !Sub '${QSS3BucketName}-${AWS::Region}', !Ref QSS3BucketName]
            Effect: Allow
          - Action:
              - 'logs:CreateLogStream'
              - 'logs:GetLogEvents'
              - 'logs:PutLogEvents'
              - 'logs:DescribeLogGroups'
              - 'logs:DescribeLogStreams'
              - 'logs:PutRetentionPolicy'
              - 'logs:PutMetricFilter'
              - 'logs:CreateLogGroup'
            Resource: !Sub "arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:${BastionMainLogGroup}:*"
            Effect: Allow
          - Action:
              - 'ec2:AssociateAddress'
              - 'ec2:DescribeAddresses'
            Resource: '*'
            Effect: Allow
      Roles: 
        - !Ref BastionHostRole
  BastionHostProfile:
    DependsOn: BastionHostPolicy
    Type: 'AWS::IAM::InstanceProfile'
    Properties:
      Roles: 
        - !Ref BastionHostRole
      Path: /
  EIP1:
    Type: 'AWS::EC2::EIP'
    Properties:
      Domain: vpc
  EIP2:
    Type: 'AWS::EC2::EIP'
    Condition: 2BastionCondition
    Properties:
      Domain: vpc
  EIP3:
    Type: 'AWS::EC2::EIP'
    Condition: 3BastionCondition
    Properties:
      Domain: vpc
  EIP4:
    Type: 'AWS::EC2::EIP'
    Condition: 4BastionCondition
    Properties:
      Domain: vpc
  BastionAutoScalingGroup:
    Type: 'AWS::AutoScaling::AutoScalingGroup'
    Properties:
      LaunchConfigurationName: !Ref BastionLaunchConfiguration
      VPCZoneIdentifier: 
        - !Ref PublicSubnet1Id
        - !Ref PublicSubnet2Id
      MinSize: !Ref NumBastionHosts
      MaxSize: !Ref NumBastionHosts
      Cooldown: '900'
      DesiredCapacity: !Ref NumBastionHosts
      Tags:
        - Key: Name
          Value: !Ref BastionHostName
          PropagateAtLaunch: true
    CreationPolicy:
      ResourceSignal:
        Count: !Ref NumBastionHosts
        Timeout: PT60M
      AutoScalingCreationPolicy:
        MinSuccessfulInstancesPercent: 100
    UpdatePolicy:
      AutoScalingReplacingUpdate:
        WillReplace: true
  BastionLaunchConfiguration:
    Type: 'AWS::AutoScaling::LaunchConfiguration'
    Metadata:
      'AWS::CloudFormation::Authentication':
        S3AccessCreds:
          type: S3
          roleName: !Ref BastionHostRole
          buckets:
            - !If [UsingDefaultBucket, !Sub '${QSS3BucketName}-${AWS::Region}', !Ref QSS3BucketName]
      'AWS::CloudFormation::Init':
        config:
          files:
            /tmp/auditd.rules:
              mode: '000550'
              owner: root
              group: root
              content: |
                -a exit,always -F arch=b64 -S execve
                -a exit,always -F arch=b32 -S execve
            /tmp/auditing_configure.sh:
              source: !Sub
                - https://${S3Bucket}.s3.${S3Region}.${AWS::URLSuffix}/${QSS3KeyPrefix}scripts/auditing_configure.sh
                - S3Bucket: !If
                    - UsingDefaultBucket
                    - !Sub 'aws-quickstart-${AWS::Region}'
                    - !Ref 'QSS3BucketName'
                  S3Region: !If
                    - UsingDefaultBucket
                    - !Ref 'AWS::Region'
                    - !Ref 'QSS3BucketRegion'
              mode: '000550'
              owner: root
              group: root
              authentication: S3AccessCreds
            /tmp/bastion_bootstrap.sh:
              source: !If
                - UseAlternativeInitialization
                - !Ref AlternativeInitializationScript
                - !Sub
                  - https://${S3Bucket}.s3.${S3Region}.${AWS::URLSuffix}/${QSS3KeyPrefix}scripts/bastion_bootstrap.sh
                  - S3Bucket: !If
                      - UsingDefaultBucket
                      - !Sub 'aws-quickstart-${AWS::Region}'
                      - !Ref 'QSS3BucketName'
                    S3Region: !If
                      - UsingDefaultBucket
                      - !Ref 'AWS::Region'
                      - !Ref 'QSS3BucketRegion'
              mode: '000550'
              owner: root
              group: root
              authentication: S3AccessCreds
          commands:
            a-add_auditd_rules:
              cwd: '/tmp/'
              env:
                BASTION_OS: !FindInMap [LinuxAMINameMap, !Ref BastionAMIOS, OS]
              command: "./auditing_configure.sh"
              # command:
              #   - !If [ ]
              #   - "cat /tmp/auditd.rules >> /etc/audit/rules.d/audit.rules && service auditd restart"
            b-bootstrap:
              cwd: '/tmp/'
              env:
                REGION: !Sub ${AWS::Region}
                URL_SUFFIX: !Sub ${AWS::URLSuffix}
                BANNER_REGION: !If [ UsingDefaultBucket, !Ref 'AWS::Region', !Ref 'QSS3BucketRegion' ]
              command: !Sub
                - "./bastion_bootstrap.sh --banner ${BannerUrl} --enable ${EnableBanner} --tcp-forwarding ${EnableTCPForwarding} --x11-forwarding ${EnableX11Forwarding}"
                - BannerUrl: !If
                  - DefaultBanner
                  - !Sub
                    - s3://${S3Bucket}/${QSS3KeyPrefix}scripts/banner_message.txt
                    - S3Bucket: !If [ UsingDefaultBucket, !Sub 'aws-quickstart-${AWS::Region}', !Ref 'QSS3BucketName' ]
                  - !Ref BastionBanner
    Properties:
      AssociatePublicIpAddress: true
      PlacementTenancy: !Ref BastionTenancy
      KeyName: !Ref KeyPairName
      IamInstanceProfile: !Ref BastionHostProfile
      ImageId: !If
        - UseOSImageOverride
        - !Ref OSImageOverride
        - !FindInMap
          - AWSAMIRegionMap
          - !Ref 'AWS::Region'
          - !FindInMap
            - LinuxAMINameMap
            - !Ref BastionAMIOS
            - Code
      SecurityGroups:
        - !Ref BastionSecurityGroup
      InstanceType: !Ref BastionInstanceType
      BlockDeviceMappings:
        - DeviceName: /dev/xvda
          Ebs:
            VolumeSize: !Ref RootVolumeSize
            VolumeType: gp2
            Encrypted: true
            DeleteOnTermination: true
      UserData:
        Fn::Base64: !Sub
          - |
            #!/bin/bash
            set -x
            for e in $(echo "${EnvironmentVariables}" | tr ',' ' '); do
              export $e
            done
            export PATH=$PATH:/usr/local/bin
            #cfn signaling functions
            yum install git -y || apt-get install -y git || zypper -n install git

            function cfn_fail
            {
              cfn-signal -e 1 --stack ${AWS::StackName} --region ${AWS::Region} --resource ${LogicalId}BastionAutoScalingGroup
              exit 1
            }

            function cfn_success
            {
              cfn-signal -e 0 --stack ${AWS::StackName} --region ${AWS::Region} --resource ${LogicalId}BastionAutoScalingGroup
              exit 0
            }

            until git clone https://github.com/aws-quickstart/quickstart-linux-utilities.git ; do echo "Retrying"; done
            cd /quickstart-linux-utilities;
            source quickstart-cfn-tools.source;
            qs_update-os || qs_err;
            qs_bootstrap_pip || qs_err " pip bootstrap failed ";
            qs_aws-cfn-bootstrap || qs_err " cfn bootstrap failed ";

            EIP_LIST="${EIP1},${EIP2},${EIP3},${EIP4}"
            CLOUDWATCHGROUP=${BastionMainLogGroup}
            cfn-init -v --stack '${AWS::StackName}' --resource ${LogicalId}BastionLaunchConfiguration --region ${AWS::Region} || cfn_fail
            [ $(qs_status) == 0 ] && cfn_success || cfn_fail
          - EIP2:
              !If
              - 2BastionCondition
              - !Ref EIP2
              - 'Null'
            EIP3:
              !If
              - 3BastionCondition
              - !Ref EIP3
              - 'Null'
            EIP4:
              !If
              - 4BastionCondition
              - !Ref EIP4
              - 'Null'
  BastionSecurityGroup:
    Type: 'AWS::EC2::SecurityGroup'
    Properties:
      GroupDescription: Enables SSH Access to Bastion Hosts
      VpcId: !Ref VPCID
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: !Ref RemoteAccessCIDR
        - IpProtocol: icmp
          FromPort: -1
          ToPort: -1
          CidrIp: !Ref RemoteAccessCIDR
Outputs:
  BastionAutoScalingGroup:
    Description: Auto Scaling Group Reference ID
    Value: !Ref BastionAutoScalingGroup
  EIP1:
    Description: Elastic IP 1 for Bastion
    Value: !Ref EIP1
  EIP2:
    Condition: 2BastionCondition
    Description: Elastic IP 2 for Bastion
    Value: !Ref EIP2
  EIP3:
    Condition: 3BastionCondition
    Description: Elastic IP 3 for Bastion
    Value: !Ref EIP3
  EIP4:
    Condition: 4BastionCondition
    Description: Elastic IP 4 for Bastion
    Value: !Ref EIP4
  CloudWatchLogs:
    Description: CloudWatch Logs GroupName. Your SSH logs will be stored here.
    Value: !Ref BastionMainLogGroup
  BastionSecurityGroupID:
    Description: Bastion Security Group ID
    Value: !Ref BastionSecurityGroup
  BastionHostRole:
    Description: Bastion IAM Role name
    Value: !Ref BastionHostRole
