in source/labs/msk-client-setup.ts [27:241]
constructor(scope: cdk.Construct, id: string, props: SolutionStackProps) {
super(scope, id, props);
const keyPair = new cdk.CfnParameter(this, 'KeyPair', {
type: 'AWS::EC2::KeyPair::KeyName'
});
const latestAmiId = new cdk.CfnParameter(this, 'LatestAmiId', {
type: 'AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>',
default: '/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2'
});
const userDataLocation = new cdk.CfnParameter(this, 'UserDataLocation', {
type: 'String',
default: 'https://github.com/aws-samples/lab-resources-for-amazon-msk'
});
const setupScript = new cdk.CfnParameter(this, 'SetupScript', {
type: 'String',
default: './setup.sh'
});
const roleName = new cdk.CfnParameter(this, 'RoleName', {
type: 'String',
allowedPattern: '.+',
constraintDescription: 'Role name must not be empty'
});
//---------------------------------------------------------------------
// Template metadata
this.templateOptions.metadata = {
'AWS::CloudFormation::Interface': {
ParameterGroups: [
{
Label: { default: 'Amazon EC2 client configuration' },
Parameters: [
keyPair.logicalId,
latestAmiId.logicalId,
userDataLocation.logicalId,
setupScript.logicalId,
roleName.logicalId
]
}
],
ParameterLabels: {
[keyPair.logicalId]: {
default: 'Name of an existing key pair to enable SSH access to the instance'
},
[latestAmiId.logicalId]: {
default: 'Amazon Machine Image for the instance'
},
[userDataLocation.logicalId]: {
default: 'Git repository where the user data configuration is stored (default branch will be used)'
},
[setupScript.logicalId]: {
default: 'Path of the shell script to be executed when the instance launches'
},
[roleName.logicalId]: {
default: 'Name of an existing IAM role to associate with the instance'
}
}
}
};
//---------------------------------------------------------------------
// MSK VPC
const mskVpc = new cfninc.CfnInclude(this, 'MSKVPCStack', {
templateFile: 'labs/templates/MSKPrivateVPCOnly.yml'
});
//---------------------------------------------------------------------
// Cloud9 custom resource
const executionRole = new ExecutionRole(this, 'CustomResourceRole', {
inlinePolicyName: 'Cloud9IAM',
inlinePolicyDocument: new iam.PolicyDocument({
statements: [
new iam.PolicyStatement({
actions: [
'iam:AddRoleToInstanceProfile',
'iam:AttachRolePolicy',
'iam:CreateInstanceProfile',
'iam:CreateRole',
'iam:GetInstanceProfile',
'iam:GetRole',
// Even though PassRole is not used directly, it's required by AddRoleToInstanceProfile
'iam:PassRole'
],
resources: [
`arn:${cdk.Aws.PARTITION}:iam::${cdk.Aws.ACCOUNT_ID}:role/service-role/AWSCloud9SSMAccessRole`,
`arn:${cdk.Aws.PARTITION}:iam::${cdk.Aws.ACCOUNT_ID}:instance-profile/cloud9/AWSCloud9SSMInstanceProfile`
],
effect: iam.Effect.ALLOW
})
]
})
});
const cloud9Setup = new lambda.Function(this, 'CustomResource', {
runtime: lambda.Runtime.PYTHON_3_8,
handler: 'lambda_function.handler',
description: 'This function creates prerequisite resources for Cloud9 (such as IAM roles)',
code: lambda.Code.fromAsset('lambda/cloud9-setup'),
timeout: cdk.Duration.minutes(1),
role: executionRole.Role
});
const cloud9CR = new cdk.CustomResource(this, 'Cloud9Helper', {
serviceToken: cloud9Setup.functionArn,
resourceType: 'Custom::Cloud9Setup'
});
//---------------------------------------------------------------------
// Cloud9 environment
const cloud9Env = new cloud9.CfnEnvironmentEC2(this, 'Cloud9EC2', {
automaticStopTimeMinutes: 600,
connectionType: 'CONNECT_SSM',
description: 'Cloud9 EC2 environment',
instanceType: 'm5.large',
imageId: 'amazonlinux-2-x86_64',
name: `${cdk.Aws.STACK_NAME}-Cloud9EC2Bastion`,
subnetId: cdk.Fn.ref('PublicSubnetOne'),
tags: [{
key: 'Purpose',
value: 'Cloud9EC2BastionHostInstance'
}]
});
cloud9Env.node.addDependency(cloud9CR);
//---------------------------------------------------------------------
// MSK client instance
const kafkaClientSG = new ec2.CfnSecurityGroup(this, 'KafkaClientInstanceSecurityGroup', {
vpcId: cdk.Fn.ref('VPC'),
groupDescription: 'EC2 Client Security Group',
securityGroupIngress: [{
ipProtocol: 'tcp',
fromPort: 22,
toPort: 22,
cidrIp: cdk.Fn.findInMap('SubnetConfig', 'PublicOne', 'CIDR'),
description: 'Enable SSH access via port 22 from VPC'
}]
});
CfnNagHelper.addSuppressions(kafkaClientSG, {
Id: 'W9',
Reason: 'Access is restricted to the public subnet where the Cloud9 environment is located'
});
new ec2.CfnSecurityGroupIngress(this, 'KafkaClientInstanceSecurityGroup8081', {
groupId: kafkaClientSG.attrGroupId,
sourceSecurityGroupId: kafkaClientSG.attrGroupId,
description: 'Schema Registry access inside the security group',
ipProtocol: 'tcp',
fromPort: 8081,
toPort: 8081
});
const instanceProfile = new iam.CfnInstanceProfile(this, 'EC2InstanceProfile', { roles: [roleName.valueAsString] });
const userDataCommands = [
'#!/bin/bash',
'yum install git -y',
'cd /home && mkdir labs-resources',
`git clone ${userDataLocation.valueAsString} labs-resources && cd $_`,
`chmod +x ${setupScript.valueAsString} && ${setupScript.valueAsString}`
];
const kafkaClient = new ec2.CfnInstance(this, 'KafkaClientEC2Instance', {
instanceType: 'm5.large',
keyName: keyPair.valueAsString,
subnetId: cdk.Fn.ref('PrivateSubnetMSKOne'),
securityGroupIds: [kafkaClientSG.attrGroupId],
imageId: latestAmiId.valueAsString,
tags: [{ key: 'Name', value: 'KafkaClientInstance' }],
iamInstanceProfile: instanceProfile.ref,
userData: cdk.Fn.base64(userDataCommands.join('\n'))
});
// If the instance is created before the route table (to the NAT Gateway) is available,
// any commands that reach the internet (e.g. yum) will fail.
kafkaClient.addDependsOn(mskVpc.getResource('PrivateRoute'));
//---------------------------------------------------------------------
// Solution metrics
new SolutionHelper(this, 'SolutionHelper', {
solutionId: props.solutionId,
pattern: MskClientStack.name
});
//---------------------------------------------------------------------
// Outputs
new cdk.CfnOutput(this, 'SSHKafkaClientEC2Instance', {
value: `ssh -A ec2-user@${kafkaClient.attrPrivateDnsName}`,
description: 'SSH command for the EC2 instance',
exportName: `${cdk.Aws.STACK_NAME}-SSHKafkaClientEC2Instance`
});
new cdk.CfnOutput(this, 'KafkaClientEC2InstancePrivateDNS', {
value: kafkaClient.attrPrivateDnsName,
description: 'The private DNS for the EC2 instance'
});
new cdk.CfnOutput(this, 'KafkaClientEC2InstanceSecurityGroupId', {
value: kafkaClientSG.attrGroupId,
description: 'ID of the security group for the EC2 instance',
exportName: `${cdk.Aws.STACK_NAME}-KafkaClientEC2InstanceSecurityGroupId`
});
new cdk.CfnOutput(this, 'SchemaRegistryUrl', {
value: `http://${kafkaClient.attrPrivateDnsName}:8081`,
description: 'Url for the Schema Registry',
exportName: `${cdk.Aws.STACK_NAME}-SchemaRegistryUrl`
});
}