As you design your parameters, follow the guidelines outlined here.

Use parameters and setting defaults

Parameterize as much as possible, cover settings that you expect customers to customize. Some examples are CIDR blocks, FQDN names, host names, instance types, and storage volume sizes.

When setting defaults for instance types, make sure that the default is available in all or most of the AWS Regions. To check availability, refer to Amazon EC2 On-Demand Pricing.

Follow standards for naming, labeling, and grouping

For parameter names, labels, and groups, follow the guidelines in the Parameter naming standards section of this guide.

Use parameter types

Use AWS-specific parameter types as much as possible. This enables customers to pick values from a dropdown list. For details, refer to Parameters in the AWS CloudFormation documentation.

Use the DependsOn attribute

Use the DependsOn attribute appropriately across resources to control the order of resource creation. Be mindful of the situation when a DependsOn attribute is required.

Add Partner Solution portability parameters

Include the standard parameters for the Partner Solution S3 bucket name, bucket Region, and key prefix.

Set the default value for the key prefix to quickstart-companyname-productname/, e.g., quickstart-microsoft-rdgateway/.


...
    QSS3BucketName:
      AllowedPattern: ^[0-9a-z]+([0-9a-z-\.]*[0-9a-z])*$
      ConstraintDescription:
         The S3 bucket name can include numbers, lowercase letters, 
         and hyphens (-), but it cannot start or end with a hyphen.
      Default: aws-quickstart
      Description: >-
         Name of the S3 bucket for your copy of the deployment assets. Keep the default
         name unless you are customizing the template. Changing the name updates code 
         references to point to a new location.
      MinLength: 3
      MaxLength: 63
      Type: String
    QSS3BucketRegion:
      Default: us-east-1
      Description: >-
         AWS Region where the S3 bucket (QSS3BucketName) is hosted. Keep 
         the default Region unless you are customizing the template. Changing the Region 
         updates code references to point to a new location. When using your own bucket, 
         specify the Region.
      Type: String
    QSS3KeyPrefix:
      AllowedPattern: ^([0-9a-zA-Z!-_\.\*'\(\)/]+/)*$
      ConstraintDescription:
         The S3 key prefix can include numbers, lowercase letters, uppercase letters, 
         hyphens (-), underscores (_), periods (.), asterisks (*), single quotes ('),
         open parenthesis ((), close parenthesis ()), and forward slashes (/). End the
         prefix with a forward slash.
      Default: quickstart-ibm-security-guardium-insights/
      Description: >-
         S3 key prefix that is used to simulate a folder for your copy of the 
         deployment assets. Keep the default prefix unless you are customizing 
         the template. Changing the prefix updates code references to point to 
         a new location.
      Type: String
...

Use the AWS CloudFormation Fn::Sub function to join the Partner Solution S3 bucket name and key prefix values. Here’s an example using the short form (!Sub):


TemplateURL: !Sub 
  - https://${S3Bucket}.s3.${S3Region}.${AWS::URLSuffix}/${QSS3KeyPrefix}templates/cribl-single-x86.workload.template.yaml
   

Use an IAM role with a policy that allows GetObject to ${S3Bucket}/${QSS3KeyPrefix}*. Example:


  BastionHostRole:
    Condition: CreateIAMRole
    Type: AWS::IAM::Role
    Properties:
      Path: !If [RolePathProvided, !Ref RolePath, !Ref AWS::NoValue]
      PermissionsBoundary:
        !If [PermissionsBoundaryProvided, !Ref PermissionsBoundaryArn, !Ref AWS::NoValue]
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action: sts:AssumeRole
            Principal:
              Service:
                - !Sub ec2.${AWS::URLSuffix}
      ManagedPolicyArns:
        - !Sub arn:${AWS::Partition}:iam::aws:policy/AmazonSSMManagedInstanceCore
        - !Sub arn:${AWS::Partition}:iam::aws:policy/CloudWatchAgentServerPolicy
  BastionHostPolicy:
    Type: AWS::IAM::Policy
    Condition: CreateIAMRole
    Properties:
      PolicyName: BastionPolicy
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Sid: ListQSS3BucketObjects
            Effect: Allow
            Action: s3:ListBucket
            Resource: !Sub
              - arn:${AWS::Partition}:s3:::${S3Bucket}
              - S3Bucket: !If [UsingDefaultBucket, !Sub '${QSS3BucketName}-${AWS::Region}', !Ref QSS3BucketName]
          - Sid: GetQSS3Objects
            Effect: Allow
            Action:
              - s3:GetObject
              - s3:GetObjectVersion
            Resource: !Sub
              - arn:${AWS::Partition}:s3:::${S3Bucket}/${QSS3KeyPrefix}*
              - S3Bucket: !If [UsingDefaultBucket, !Sub '${QSS3BucketName}-${AWS::Region}', !Ref QSS3BucketName]
          - Sid: WriteToCloudWatchLogs
            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}:*
          - Sid: Global
            Effect: Allow
            Action:
              - ec2:DescribeAddresses
            Resource: '*'
          - !If
              - HasRemoteCIDR
              - Sid: AssociateStackEips
                Effect: Allow
                Action:
                  - ec2:AssociateAddress
                Resource: '*'
                Condition:
                  StringEquals:
                    ec2:ResourceTag/aws:cloudformation:stack-id: !Ref AWS::StackId
              - !Ref AWS::NoValue
      Roles:
        - !If [CreateIAMRole, !Ref BastionHostRole, !Ref AlternativeIAMRole]
   

Associate the instance with the instance profile from the IAM role. Here’s an example based on the previous code:


BastionLaunchTemplate:
    Type: AWS::EC2::LaunchTemplate
    Metadata:...
    Properties:
      LaunchTemplateData:
        Placement:
          Tenancy: !Ref BastionTenancy
        KeyName: !If [UseKeyPair, !Ref KeyPairName, !Ref AWS::NoValue]
        ImageId: !If
          - UseOSImageOverride
          - !Ref OSImageOverride
          - !FindInMap [AWSAMIRegionMap, !Ref AWS::Region, !FindInMap [LinuxAMINameMap, !Ref BastionAMIOS, Code]]
        InstanceType: !Ref BastionInstanceType
        IamInstanceProfile:
          Arn: !GetAtt BastionHostProfile.Arn
...

Use AWS CloudFormation authentication with the IAM role, and use cfn-init to access resources. Example:


   Metadata:
      AWS::CloudFormation::Authentication:
        S3AccessCreds:
          type: S3
          roleName: !If [CreateIAMRole, !Ref BastionHostRole, !Ref AlternativeIAMRole]
          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
            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:
      LaunchTemplateData:
        Placement:
          Tenancy: !Ref BastionTenancy
        KeyName: !If [UseKeyPair, !Ref KeyPairName, !Ref AWS::NoValue]
        ImageId: !If
          - UseOSImageOverride
          - !Ref OSImageOverride
          - !FindInMap [AWSAMIRegionMap, !Ref AWS::Region, !FindInMap [LinuxAMINameMap, !Ref BastionAMIOS, Code]]
        InstanceType: !Ref BastionInstanceType
        IamInstanceProfile:
          Arn: !GetAtt BastionHostProfile.Arn
        NetworkInterfaces:
          - DeviceIndex: 0
            AssociatePublicIpAddress: !If [HasRemoteCIDR, true, false]
            Groups:
              - !Ref BastionSecurityGroup
        BlockDeviceMappings:
          - DeviceName: /dev/xvda
            Ebs:
              VolumeSize: !Ref RootVolumeSize
              VolumeType: gp2
              Encrypted: true
              DeleteOnTermination: true
        UserData:
          Fn::Base64: !Sub
            - |
              #!/usr/bin/env bash
              set -x
              for e in $(echo "${EnvironmentVariables}" | tr ',' ' '); do
                export $e
                echo "$e" >> /root/.bashrc
              done
              export PATH=$PATH:/usr/local/bin
              yum install -y git unzip wget curl || apt-get install -y git unzip wget curl || zypper -n install git unzip wget curl
              #cfn signaling functions
              cfn_fail() {
                cfn-signal -e 1 --stack ${AWS::StackName} --region ${AWS::Region} --resource BastionAutoScalingGroup
                exit 1
              }
              cfn_success() {
                cfn-signal -e 0 --stack ${AWS::StackName} --region ${AWS::Region} --resource BastionAutoScalingGroup
                exit 0
              }
              pushd /tmp
              if [[ "a$(which aws)" == "a" ]]
              then
                echo "Installing AWS CLI..."
                uname=$(uname -m)
                wget -nv -O "./awscliv2.zip" "https://awscli.amazonaws.com/awscli-exe-linux-$uname.zip"
                unzip -q ./awscliv2.zip
                ./aws/install
              fi
              until aws s3 cp --no-progress --region ${AWS::Region} "s3://${S3Bucket}/${QSS3KeyPrefix}scripts/cfn-tools.sh" .
              do
                echo "Retrying..."
              done
              source ./cfn-tools.sh
              popd /tmp
              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 BastionLaunchTemplate --region ${AWS::Region} || cfn_fail
              [ $(qs_status) == 0 ] && cfn_success || cfn_fail
            - EIP1: !If [HasRemoteCIDR, !Ref EIP1, 'Null']
              EIP2: !If [2BastionCondition, !Ref EIP2, 'Null']
              EIP3: !If [3BastionCondition, !Ref EIP3, 'Null']
              EIP4: !If [4BastionCondition, !Ref EIP4, 'Null']
              S3Bucket: !If [UsingDefaultBucket, !Sub 'aws-quickstart-${AWS::Region}', !Ref QSS3BucketName]