AWSTemplateFormatVersion: '2010-09-09' Description: 'Amazon Redshift POC resource auto-creation (To be deployed in your AWS account to execute the Redshift POC)' Metadata: AWS::CloudFormation::Interface: ParameterGroups: - Label: default: "Target Infrastructure" Parameters: - ConfigurationFile - Label: default: "Staging Infrastructure" Parameters: - EC2InstanceAMI - KeyPair - OnPremisesCIDR - SubnetID - Label: default: "Secret input" Parameters: - SourceDBPassword - TargetRedshiftPassword ParameterLabels: ConfigurationFile: default: "Configuration File" EC2InstanceAMI: default: "EC2 AMI" KeyPair: default: "Key Pair" OnPremisesCIDR: default: "On Prem CIDR" SubnetID: default: "Subnet ID" SourceDBPassword: default: "Source Password" type: String TargetRedshiftPassword: default: "Redshift Password" type: String Conditions: DBHasValue: !Not - !Equals - !Ref SourceDBPassword - '' RedshiftHasValue: !Not - !Equals - !Ref TargetRedshiftPassword - '' Parameters: SubnetID: Description: Select the subnet to launch the first staging instance in - ensure it is public and public IPs are auto-assigned Type: AWS::EC2::Subnet::Id ConfigurationFile: Description: The location (URI) for the configuration file on S3 Type: String EC2InstanceAMI: Description: AMI for the Amazon Linux 2 based EC2 instance. Please don't change this parameter unless needed for some compliance requirement. Type: "AWS::SSM::Parameter::Value" Default: "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2" KeyPair: Description: Name of the keypair created in your account to be used in case you need to login to the EC2 instance Type: AWS::EC2::KeyPair::KeyName OnPremisesCIDR: Description: IP range (CIDR notation) of the infrastructure which needs to access the staging EC2 Type: String Default: 10.0.0.0/8 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 IP CIDR range of the form x.x.x.x/x. SourceDBPassword: Description: Password for the source system outside the target infrastructure (Optional -- use when connecting to external database) Default: '' Type: String NoEcho: 'true' TargetRedshiftPassword: Description: Password for the Redshfit cluster used in the architecture (Optional -- use when connecting to existing Redshift cluster) Default: '' Type: String NoEcho: 'true' Resources: SQLSecrets: Type: AWS::SecretsManager::Secret Condition: DBHasValue Properties: Name: 'SourceDBPassword' Description: 'Credentials for source system in DMS demo' SecretString: !Sub ${SourceDBPassword} RedshiftSecrets: Type: AWS::SecretsManager::Secret Condition: RedshiftHasValue Properties: Name: 'RedshiftPassword' Description: 'Redshift Cluster Secret' SecretString: !Sub ${TargetRedshiftPassword} LambdaExecutionRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - sts:AssumeRole Path: "/" Policies: - PolicyName: root PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - logs:CreateLogGroup - logs:CreateLogStream - logs:PutLogEvents Resource: arn:aws:logs:*:*:* - Effect: Allow Action: - ec2:DescribeSubnets - ec2:DescribeVpcs Resource: "*" # NOTE: Pay special attention to the indentatiion in the Python code below. # Lines that appear blank are likely not blank, but have leading spaces. GetAttFromParam: Type: AWS::Lambda::Function Properties: Description: Look up info from a VPC or subnet ID Handler: index.handler MemorySize: 128 Role: !GetAtt LambdaExecutionRole.Arn Runtime: "python3.6" Timeout: 30 Code: ZipFile: | import json import boto3 import cfnresponse import logging def handler(event, context): logger = logging.getLogger() logger.setLevel(logging.INFO) # initialize our responses, assume failure by default response_data = {} response_status = cfnresponse.FAILED logger.info('Received event: {}'.format(json.dumps(event))) if event['RequestType'] == 'Delete': response_status = cfnresponse.SUCCESS cfnresponse.send(event, context, response_status, response_data) try: ec2=boto3.client('ec2') except Exception as e: logger.info('boto3.client failure: {}'.format(e)) cfnresponse.send(event, context, response_status, response_data) name_filter = event['ResourceProperties']['NameFilter'] name_filter_parts = name_filter.split('-') resource_type=name_filter_parts[0] try: subnets = ec2.describe_subnets(SubnetIds=[name_filter]) except Exception as e: logger.info('ec2.describe_subnets failure: {}'.format(e)) cfnresponse.send(event, context, response_status, response_data) number_of_subnets = len(subnets['Subnets']) logger.info('number of subnets returned: {}'.format(number_of_subnets)) if number_of_subnets == 1: VpcId = subnets['Subnets'][0]['VpcId'] response_data['VpcId'] = VpcId logger.info('subnet VpcId {}'.format(VpcId)) response_status = cfnresponse.SUCCESS cfnresponse.send(event, context, response_status, response_data) elif number_of_subnets == 0: logger.info('no matching subnet for filter {}'.format(name_filter)) cfnresponse.send(event, context, response_status, response_data) else: logger.info('multiple matching subnets for filter {}'.format(name_filter)) cfnresponse.send(event, context, response_status, response_data) SubnetInfo: Type: Custom::SubnetInfo Properties: ServiceToken: !GetAtt GetAttFromParam.Arn NameFilter: !Ref SubnetID DMSFullAccessAAA: Type: AWS::IAM::ManagedPolicy Properties: Description: Policy for creating DMS resources Path: / PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: "dms:*" Resource: "*" RootRole: 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/IAMFullAccess" - "arn:aws:iam::aws:policy/AWSCloudFormationFullAccess" - "arn:aws:iam::aws:policy/AmazonSSMFullAccess" - "arn:aws:iam::aws:policy/AmazonRedshiftFullAccess" - "arn:aws:iam::aws:policy/AmazonS3FullAccess" - "arn:aws:iam::aws:policy/SecretsManagerReadWrite" - "arn:aws:iam::aws:policy/AmazonEC2FullAccess" - Ref: DMSFullAccessAAA InstanceProfileEC2Instance: Type: AWS::IAM::InstanceProfile Properties: Path: "/" Roles: - Ref: RootRole SecurityGroupEc2: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: 'Launching EC2 security group' SecurityGroupIngress: - CidrIp: !Ref OnPremisesCIDR Description : Allow inbound access for on prem users on SSH port for the launching EC2 instance IpProtocol: tcp FromPort: 22 IpProtocol: tcp ToPort: 22 VpcId: !GetAtt SubnetInfo.VpcId SecurityGroupSelfReference: Type: AWS::EC2::SecurityGroupIngress Properties: Description: Self Referencing Rule FromPort: -1 IpProtocol: -1 GroupId: !GetAtt [SecurityGroupEc2, GroupId] SourceSecurityGroupId: !GetAtt [SecurityGroupEc2, GroupId] ToPort: -1 StagingEC2Instance: Type: AWS::EC2::Instance CreationPolicy: ResourceSignal: Timeout: PT45M Properties: KeyName: !Ref KeyPair InstanceType: "t3.micro" IamInstanceProfile: !Ref InstanceProfileEC2Instance Tags: - Key: Name Value: Fn::Join: - "-" - - Ref: AWS::StackName - EC2Instance BlockDeviceMappings: - DeviceName: "/dev/sda1" Ebs: DeleteOnTermination: true VolumeType: gp2 VolumeSize: 30 ImageId: !Ref EC2InstanceAMI NetworkInterfaces: - DeleteOnTermination: true DeviceIndex: "0" SubnetId: !Ref SubnetID GroupSet: - Ref: SecurityGroupEc2 UserData: Fn::Base64: !Sub | #!/bin/bash -e yum update -y yum -y install git yum -y install python3 yum -y install python3-pip yum -y install aws-cfn-bootstrap yum -y install gcc gcc-c++ python3 python3-devel unixODBC unixODBC-devel mkdir /root/.aws echo "[default]" > /root/.aws/config echo "region = ${AWS::Region}" >> /root/.aws/config curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.34.0/install.sh | bash . /.nvm/nvm.sh nvm install node npm install -g aws-cdk git clone -b main https://github.com/aws-samples/amazon-redshift-infrastructure-automation.git cd amazon-redshift-infrastructure-automation python3 -m venv .env source .env/bin/activate pip install -r requirements.txt pip install aws_cdk.aws_dms pip install aws_cdk.aws_redshift pip install boto3 pip install aws_cdk.aws_cloudformation pip install aws_cdk.custom_resources pip install aws_cdk.aws_glue aws s3 cp ${ConfigurationFile} ./user-config.json # # Run CDK App # export STACK_NAME=${AWS::StackName} cdk deploy --all --require-approval never deactivate /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource StagingEC2Instance --region ${AWS::Region} Outputs: SourceAccountNumber: Description: "Extract Source Account Number" Value: !Ref AWS::AccountId VPC: Description: "VPC used" Value: !GetAtt SubnetInfo.VpcId