Deploy Gateways Via AWS Organizations With CloudFormation StackSets

Last modified on March 11, 2024

Overview

This guide describes how to deploy StrongDM gateways across multiple AWS accounts within an AWS organization using CloudFormation StackSets.

Prerequisites

  • StrongDM Administrator account
  • StrongDM admin token with the ability to list and create gateways
  • AWS organization set up with multiple member accounts
  • AWS Identity and Access Management (IAM) role with permissions to create and manage StackSets
  • Basic knowledge of AWS CloudFormation and StackSets

Procedure

Prepare CloudFormation templates

You may leverage this YAML template as the basis for one that would function in your AWS environment:

AWSTemplateFormatVersion: '2010-09-09'
Description: StrongDM self-registering gateway with VPC creation
Parameters:
  # StrongDM variables
  SDMListenPort:
    Type: Number
    Default: 5000
    MinValue: 1024
    MaxValue: 65535
    Description: The TCP port that will be exposed to the internet
  SDMAdminToken:
    AllowedPattern: (.+)
    Type: String
    Description: Paste your StrongDM admin token to create the gateway
  LatestAmiId:
    Type: 'AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>'
    Default: '/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2'
  CommonVpcCIDR:
    Type: String
    MinLength: 9
    MaxLength: 18
    AllowedPattern: "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})"
    ConstraintDescription: Must be a valid CIDR range in the form x.x.x.x/x
    Default: 10.112.0.0/16
  PublicACIDR:
    Type: String
    MinLength: 9
    MaxLength: 18
    AllowedPattern: "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})"
    ConstraintDescription: Must be a valid CIDR range in the form x.x.x.x/x
    Default: 10.112.0.0/22
  PublicBCIDR:
    Type: String
    MinLength: 9
    MaxLength: 18
    AllowedPattern: "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})"
    ConstraintDescription: Must be a valid CIDR range in the form x.x.x.x/x
    Default: 10.112.4.0/22
  PrivateACIDR:
    Type: String
    MinLength: 9
    MaxLength: 18
    AllowedPattern: "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})"
    ConstraintDescription: Must be a valid CIDR range in the form x.x.x.x/x
    Default: 10.112.12.0/22
  PrivateBCIDR:
    Type: String
    MinLength: 9
    MaxLength: 18
    AllowedPattern: "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})"
    ConstraintDescription: Must be a valid CIDR range in the form x.x.x.x/x
    Default: 10.112.16.0/22
# Organization structure for parameters
Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      -
        Label:
          default: "StrongDM Configuration"
        Parameters:
          - SDMAdminToken
          - SDMListenPort
Resources:
  VPC:
    Type: "AWS::EC2::VPC"
    Properties:
      EnableDnsSupport: true
      EnableDnsHostnames: true
      CidrBlock: !Ref CommonVpcCIDR
      Tags:
        - Key: Name
          Value: ACBL VPC
  IGW:
    Type: "AWS::EC2::InternetGateway"
  GatewayAttach:
    Type: "AWS::EC2::VPCGatewayAttachment"
    Properties:
      InternetGatewayId: !Ref IGW
      VpcId: !Ref VPC
  SubnetPublicSharedA:
    Type: "AWS::EC2::Subnet"
    Properties:
      AvailabilityZone: !Select [0, !GetAZs ]
      CidrBlock: !Ref PublicACIDR
      MapPublicIpOnLaunch: true
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub "Public A - ${PublicACIDR}"
  SubnetPublicSharedB:
    Type: "AWS::EC2::Subnet"
    Properties:
      AvailabilityZone: !Select [1, !GetAZs ]
      CidrBlock: !Ref PublicBCIDR
      MapPublicIpOnLaunch: true
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub "Public B - ${PublicBCIDR}"
  SubnetPrivateSharedA:
    Type: "AWS::EC2::Subnet"
    Properties:
      AvailabilityZone: !Select [0, !GetAZs ]
      CidrBlock: !Ref PrivateACIDR
      MapPublicIpOnLaunch: false
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub "Private A - ${PrivateACIDR}"
  SubnetPrivateSharedB:
    Type: "AWS::EC2::Subnet"
    Properties:
      AvailabilityZone: !Select [1, !GetAZs ]
      CidrBlock: !Ref PrivateBCIDR
      MapPublicIpOnLaunch: false
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub "Private B - ${PrivateBCIDR}"
  SubnetRouteTableAssociatePublicA:
    Type: "AWS::EC2::SubnetRouteTableAssociation"
    Properties:
      RouteTableId: !Ref RouteTablePublic
      SubnetId: !Ref SubnetPublicSharedA
  SubnetRouteTableAssociatePublicB:
    Type: "AWS::EC2::SubnetRouteTableAssociation"
    Properties:
      RouteTableId: !Ref RouteTablePublic
      SubnetId: !Ref SubnetPublicSharedB
  SubnetRouteTableAssociatePrivateA:
    Type: "AWS::EC2::SubnetRouteTableAssociation"
    Properties:
      RouteTableId: !Ref RouteTablePrivate
      SubnetId: !Ref SubnetPrivateSharedA
  SubnetRouteTableAssociatePrivateB:
    Type: "AWS::EC2::SubnetRouteTableAssociation"
    Properties:
      RouteTableId: !Ref RouteTablePrivate
      SubnetId: !Ref SubnetPrivateSharedB
  RouteDefaultPublic:
    Type: "AWS::EC2::Route"
    DependsOn: GatewayAttach
    Properties:
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref IGW
      RouteTableId: !Ref RouteTablePublic
  RouteDefaultPrivate:
    Type: "AWS::EC2::Route"
    Properties:
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NatGateway
      RouteTableId: !Ref RouteTablePrivate
  RouteTablePublic:
    Type: "AWS::EC2::RouteTable"
    Properties:
      VpcId: !Ref VPC
  RouteTablePrivate:
    Type: "AWS::EC2::RouteTable"
    Properties:
      VpcId: !Ref VPC
  EIPNatGW:
    DependsOn: GatewayAttach
    Type: "AWS::EC2::EIP"
    Properties:
      Domain: vpc
  NatGateway:
    DependsOn: GatewayAttach
    Type: "AWS::EC2::NatGateway"
    Properties:
      AllocationId: !GetAtt EIPNatGW.AllocationId
      SubnetId: !Ref SubnetPublicSharedB
  EC2SecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: "Expose StrongDM listening port"
      VpcId: !Ref VPC
      SecurityGroupIngress:
      - IpProtocol: tcp
        FromPort: !Ref SDMListenPort
        ToPort: !Ref SDMListenPort
        CidrIp: 0.0.0.0/0
  SDMGWONE:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: t3.medium
      Tags: 
        - Key: "Name"
          Value: "StrongDM Gateway One"
      NetworkInterfaces:
        - DeviceIndex: '0'
          SubnetId: !Ref SubnetPublicSharedA
          AssociatePublicIpAddress: 'true'
          DeleteOnTermination: 'true'
          GroupSet: [!Ref EC2SecurityGroup]
      ImageId: !Ref LatestAmiId
      UserData:
        Fn::Base64:
          !Sub |
            #!/bin/bash -xe
            # set environment variables
            mkdir -p /home/ec2-user/.sdm
            touch /home/ec2-user/.sdm/sdm.log
            export TARGET_USER=ec2-user
            export SDM_LISTEN_PORT=${SDMListenPort}
            export SDM_GATEWAY_NAME=AWS-CloudFormation-$(date +%s)
            export SDM_HOSTNAME="$(curl http://169.254.169.254/latest/meta-data/public-hostname)"
            export SDM_HOME="/home/ec2-user/.sdm"
            # downloads sdm binary
            yum update -y && yum install -y unzip curl
            curl -J -O -L https://app.strongdm.com/releases/cli/linux && unzip sdmcli* && rm sdmcli*
            # Generate a gateway token
            export SDM_RELAY_TOKEN="$(./sdm --admin-token=${SDMAdminToken} relay create-gateway --name $SDM_GATEWAY_NAME $SDM_HOSTNAME:$SDM_LISTEN_PORT 0.0.0.0:$SDM_LISTEN_PORT)"
            chown -R ec2-user:ec2-user /home/ec2-user/.sdm
            # Install SDM
            ./sdm install --relay --token=$SDM_RELAY_TOKEN --user $TARGET_USER
  SDMGWTWO:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: t3.medium
      Tags: 
        - Key: "Name"
          Value: "StrongDM Gateway TWO"
      NetworkInterfaces:
        - DeviceIndex: '0'
          SubnetId: !Ref SubnetPublicSharedB
          AssociatePublicIpAddress: 'true'
          DeleteOnTermination: 'true'
          GroupSet: [!Ref EC2SecurityGroup]
      ImageId: !Ref LatestAmiId
      UserData:
        Fn::Base64:
          !Sub |
            #!/bin/bash -xe
            # set environment variables
            mkdir -p /home/ec2-user/.sdm
            touch /home/ec2-user/.sdm/sdm.log
            export TARGET_USER=ec2-user
            export SDM_LISTEN_PORT=${SDMListenPort}
            export SDM_GATEWAY_NAME=AWS-CloudFormation-$(date +%s)
            export SDM_HOSTNAME="$(curl http://169.254.169.254/latest/meta-data/public-hostname)"
            export SDM_HOME="/home/ec2-user/.sdm"
            # downloads sdm binary
            yum update -y && yum install -y unzip curl
            curl -J -O -L https://app.strongdm.com/releases/cli/linux && unzip sdmcli* && rm sdmcli*
            # Generate a gateway token
            export SDM_RELAY_TOKEN="$(./sdm --admin-token=${SDMAdminToken} relay create-gateway --name $SDM_GATEWAY_NAME $SDM_HOSTNAME:$SDM_LISTEN_PORT 0.0.0.0:$SDM_LISTEN_PORT)"
            chown -R ec2-user:ec2-user /home/ec2-user/.sdm
            # Install SDM
            ./sdm install --relay --token=$SDM_RELAY_TOKEN --user $TARGET_USER
Outputs:
  VpcId:
    Description: ID of the created VPC
    Value: !Ref VPC
  SecurityGroupId:
    Description: Security Group ID for StrongDM gateway
    Value: !GetAtt [ EC2SecurityGroup, GroupId ]

The above YAML template creates the following infrastructure components in each target account and region specified during StackSet deployment:

  • New VPC
  • Two public subnets
  • Two private subnets
  • Publicly accessible StrongDM gateway in each of the public subnets
  • Associated route tables, security groups, NAT gateways, and IGWs for the above

Every organization’s environment and architecture is unique, and the template should be modified to suit each organization as necessary.

Gather necessary parameters and input to deploy

Gather necessary information to deploy, such as the StrongDM admin token. If using the included template, you will need the following as input parameters:

  • StrongDM admin token
  • Port gateways listen on (default is 5000)
  • Private CIDR range for the new VPC (default is 10.112.0.0/16)
  • StrongDM Gateway AMI (default is the latest provided by StrongDM)
  • Private CIDR range for both private subnets

Prepare IAM permissions

Create an IAM role with permissions required for deploying resources defined in your CloudFormation templates. This role should have sufficient permissions to create EC2 instances, IAM roles, security groups, network components, and so forth, across all member accounts within the AWS organization. Assuming into child accounts via an administrator role may be an option if starting from the master root account as documented on Amazon.

Create StackSets

  1. Navigate to AWS CloudFormation service in the AWS Management Console.
  2. Select StackSets from the menu.
  3. Click on Create StackSet and choose the CloudFormation template prepared in Prepare CloudFormation templates.
  4. Specify the IAM role created in Prepare IAM Permissions as the execution role for StackSets.
  5. Configure parameters that are required in the template.
  6. Choose the AWS organization as the deployment target.

Configure deployment options

Specify deployment options, such as region availability, deployment schedule, and rollback options as per your requirements.

Deploy StackSets

Review the configuration settings and initiate the deployment of StackSets. Monitor the deployment progress in the StackSets dashboard.

Validate deployment

Once the deployment is complete, validate that StrongDM gateways are provisioned across all member accounts and target regions as configured/desired within the AWS organization.

Ensure that the gateways are functioning correctly, are shown as healthy in StrongDM, and are accessible as expected.