# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: MIT-0 AWSTemplateFormatVersion: 2010-09-09 Description: This template deploys a VPC, with a pair of public and private subnets spread across two Availability Zones. It deploys an internet gateway, with a default route on the public subnets, interface endpoint for kms, cloudwatch and secretManager, EC2 Instance, IAM role for instance, Instance profile, Security Group and SecretManager. Parameters: EnvironmentName: Description: An environment name that is prefixed to resource names Type: String VpcCIDR: Description: Please enter the IP range (CIDR notation) for this VPC Type: String Default: 10.192.0.0/16 PublicSubnet1CIDR: Description: Please enter the IP range (CIDR notation) for the public subnet in the first Availability Zone Type: String Default: 10.192.10.0/24 PublicSubnet2CIDR: Description: Please enter the IP range (CIDR notation) for the public subnet in the second Availability Zone Type: String Default: 10.192.11.0/24 PrivateSubnet1CIDR: Description: Please enter the IP range (CIDR notation) for the private subnet in the first Availability Zone Type: String Default: 10.192.20.0/24 PrivateSubnet2CIDR: Description: Please enter the IP range (CIDR notation) for the private subnet in the second Availability Zone Type: String Default: 10.192.21.0/24 KeyName: Description: Key pair name used for login into EC2 instance. Type: AWS::EC2::KeyPair::KeyName SshAllowedCidr: Description: "Cidr block for allowing ssh connection to instance. Check your public Ip using http://checkip.amazonaws.com/ and add /32 at the end of ip address" Type: String InstanceType: Description: WebServer EC2 instance type Type: String Default: t3.micro AllowedValues: [t2.nano, t2.micro, t2.small, t2.medium, t2.large, t2.xlarge, t2.2xlarge, t3.nano, t3.micro, t3.small, t3.medium, t3.large, t3.xlarge, t3.2xlarge, m4.large, m4.xlarge, m4.2xlarge, m4.4xlarge, m4.10xlarge, m5.large, m5.xlarge, m5.2xlarge, m5.4xlarge, c5.large, c5.xlarge, c5.2xlarge, c5.4xlarge, c5.9xlarge, g3.8xlarge, r5.large, r5.xlarge, r5.2xlarge, r5.4xlarge, r3.12xlarge, i3.xlarge, i3.2xlarge, i3.4xlarge, i3.8xlarge, d2.xlarge, d2.2xlarge, d2.4xlarge, d2.8xlarge] ConstraintDescription: must be a valid EC2 instance type. Mappings: RegionAMI: us-east-1: AMI: "ami-0b5eea76982371e91" us-east-2: AMI: "ami-0a606d8395a538502" us-west-1: AMI: "ami-00d8a762cb0c50254" us-west-2: AMI: "ami-0ceecbb0f30a902a6" ap-southeast-2: AMI: "ami-051a81c2bd3e755db" eu-central-1: AMI: "ami-0a261c0e5f51090b1" Resources: VPC: Type: AWS::EC2::VPC Properties: CidrBlock: !Ref VpcCIDR EnableDnsSupport: true EnableDnsHostnames: true Tags: - Key: Name Value: !Ref EnvironmentName InternetGateway: Type: AWS::EC2::InternetGateway Properties: Tags: - Key: Name Value: !Ref EnvironmentName InternetGatewayAttachment: Type: AWS::EC2::VPCGatewayAttachment Properties: InternetGatewayId: !Ref InternetGateway VpcId: !Ref VPC PublicSubnet1: Type: AWS::EC2::Subnet Properties: VpcId: !Ref VPC AvailabilityZone: !Select [ 0, !GetAZs '' ] CidrBlock: !Ref PublicSubnet1CIDR MapPublicIpOnLaunch: true Tags: - Key: Name Value: !Sub ${EnvironmentName} Public Subnet (AZ1) PublicSubnet2: Type: AWS::EC2::Subnet Properties: VpcId: !Ref VPC AvailabilityZone: !Select [ 1, !GetAZs '' ] CidrBlock: !Ref PublicSubnet2CIDR MapPublicIpOnLaunch: true Tags: - Key: Name Value: !Sub ${EnvironmentName} Public Subnet (AZ2) PrivateSubnet1: Type: AWS::EC2::Subnet Properties: VpcId: !Ref VPC AvailabilityZone: !Select [ 0, !GetAZs '' ] CidrBlock: !Ref PrivateSubnet1CIDR MapPublicIpOnLaunch: false Tags: - Key: Name Value: !Sub ${EnvironmentName} Private Subnet (AZ1) PrivateSubnet2: Type: AWS::EC2::Subnet Properties: VpcId: !Ref VPC AvailabilityZone: !Select [ 1, !GetAZs '' ] CidrBlock: !Ref PrivateSubnet2CIDR MapPublicIpOnLaunch: false Tags: - Key: Name Value: !Sub ${EnvironmentName} Private Subnet (AZ2) PublicRouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref VPC Tags: - Key: Name Value: !Sub ${EnvironmentName} Public Routes DefaultPublicRoute: Type: AWS::EC2::Route DependsOn: InternetGatewayAttachment Properties: RouteTableId: !Ref PublicRouteTable DestinationCidrBlock: 0.0.0.0/0 GatewayId: !Ref InternetGateway PublicSubnet1RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: !Ref PublicRouteTable SubnetId: !Ref PublicSubnet1 PublicSubnet2RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: !Ref PublicRouteTable SubnetId: !Ref PublicSubnet2 PrivateRouteTable1: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref VPC Tags: - Key: Name Value: !Sub ${EnvironmentName} Private Routes (AZ1) PrivateSubnet1RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: !Ref PrivateRouteTable1 SubnetId: !Ref PrivateSubnet1 PrivateSubnet2RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: !Ref PrivateRouteTable1 SubnetId: !Ref PrivateSubnet2 S3VpcEndoint: Type: AWS::EC2::VPCEndpoint Properties: ServiceName: !Sub "com.amazonaws.${AWS::Region}.s3" VpcEndpointType: Gateway VpcId: !Ref VPC RouteTableIds: - !Ref PublicRouteTable - !Ref PrivateRouteTable1 CloudWatchLogsVpcEndoint: Type: AWS::EC2::VPCEndpoint Properties: ServiceName: !Sub "com.amazonaws.${AWS::Region}.logs" VpcEndpointType: Interface VpcId: !Ref VPC PrivateDnsEnabled: true SubnetIds: - !Ref PrivateSubnet1 - !Ref PrivateSubnet2 SecurityGroupIds: - !Ref SecurityGroup CloudWatchMonitoringVpcEndoint: Type: AWS::EC2::VPCEndpoint Properties: ServiceName: !Sub "com.amazonaws.${AWS::Region}.monitoring" VpcEndpointType: Interface VpcId: !Ref VPC PrivateDnsEnabled: true SubnetIds: - !Ref PrivateSubnet1 - !Ref PrivateSubnet2 SecurityGroupIds: - !Ref SecurityGroup SecretManagerVpcEndoint: Type: AWS::EC2::VPCEndpoint Properties: ServiceName: !Sub "com.amazonaws.${AWS::Region}.secretsmanager" VpcEndpointType: Interface VpcId: !Ref VPC PrivateDnsEnabled: true SubnetIds: - !Ref PrivateSubnet1 - !Ref PrivateSubnet2 SecurityGroupIds: - !Ref SecurityGroup KmsVpcEndoint: Type: AWS::EC2::VPCEndpoint Properties: ServiceName: !Sub "com.amazonaws.${AWS::Region}.kms" VpcEndpointType: Interface VpcId: !Ref VPC PrivateDnsEnabled: true SubnetIds: - !Ref PrivateSubnet1 - !Ref PrivateSubnet2 SecurityGroupIds: - !Ref SecurityGroup SecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupName: "glue-connection-security-group" GroupDescription: "Security group with a self-referencing inbound rule." VpcId: !Ref VPC SecurityGroupIngress: Type: AWS::EC2::SecurityGroupIngress Properties: Description: Self referencing rule GroupId: !Ref SecurityGroup IpProtocol: "-1" SourceSecurityGroupId: !Ref SecurityGroup SecurityGroupEgress: Type: AWS::EC2::SecurityGroupEgress Properties: Description: Allow all outbound traffic GroupId: !Ref SecurityGroup IpProtocol: "-1" CidrIp: 0.0.0.0/0 S3Bucket: Type: 'AWS::S3::Bucket' Properties: BucketName: !Sub 'aws-glue-gluemsk-${AWS::AccountId}-${AWS::Region}' AccessControl: Private PublicAccessBlockConfiguration: BlockPublicAcls: true BlockPublicPolicy: true IgnorePublicAcls: true RestrictPublicBuckets: true S3BucketForJobOutput: Type: 'AWS::S3::Bucket' Properties: BucketName: !Sub 'aws-glue-gluemsk-output-${AWS::AccountId}-${AWS::Region}' AccessControl: Private PublicAccessBlockConfiguration: BlockPublicAcls: true BlockPublicPolicy: true IgnorePublicAcls: true RestrictPublicBuckets: true S3CleanupFunctionExecutionRole: Type: AWS::IAM::Role Properties: RoleName: !Sub "glue-msk-secleanup-role-${EnvironmentName}" ManagedPolicyArns: - "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - sts:AssumeRole Path: '/service-role/' Policies: - PolicyName: !Sub "cfn-msk-msk-s3cleanup-${EnvironmentName}" PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - iam:ListAttachedRolePolicies - iam:DetachRolePolicy Resource: - !GetAtt EC2Role.Arn - Effect: Allow Action: - s3:Get* - s3:Delete* - s3:List* Resource: - !Join - '' - - 'arn:aws:s3:::' - !Ref S3BucketForJobOutput - !Join - '' - - 'arn:aws:s3:::' - !Ref S3BucketForJobOutput - /* - !Join - '' - - 'arn:aws:s3:::' - !Ref S3Bucket - !Join - '' - - 'arn:aws:s3:::' - !Ref S3Bucket - /* Tags: - Key: AppName Value: !Sub '${EnvironmentName}-${AWS::StackName}' CleanupResourcesOnDeletion: Type: AWS::Lambda::Function Properties: Code: ZipFile: | import json import boto3 import logging import cfnresponse logger = logging.getLogger() logger.setLevel(logging.INFO) def lambda_handler(event, context): logger.info(event) responseStatus = cfnresponse.SUCCESS responseData = {} iam_role = event['ResourceProperties'].get('IamRole') bucket = event['ResourceProperties'].get('BucketName') if bucket and event['RequestType'] == 'Delete': try: s3 = boto3.resource('s3') bucket = s3.Bucket(bucket) for obj in bucket.objects.filter(): s3.Object(bucket.name, obj.key).delete() responseStatus = cfnresponse.SUCCESS except Exception as e: logger.exception(e) responseStatus = cfnresponse.FAILED if iam_role and event['RequestType'] == 'Delete': try: iam = boto3.client('iam') attached_policies = iam.list_attached_role_policies(RoleName=iam_role) for policy in attached_policies['AttachedPolicies']: iam.detach_role_policy(RoleName=iam_role, PolicyArn=policy['PolicyArn']) responseStatus = cfnresponse.SUCCESS except Exception as e: logger.exception(e) responseStatus = cfnresponse.FAILED cfnresponse.send(event, context, responseStatus, responseData) return FunctionName: !Sub "cfn-delete-resources-${EnvironmentName}" Handler: index.lambda_handler Role: !GetAtt S3CleanupFunctionExecutionRole.Arn Runtime: python3.10 Timeout: 30 CleanupS3OutputBucketOnDelete: Type: Custom::cleanups3bucket Properties: ServiceToken: !GetAtt CleanupResourcesOnDeletion.Arn BucketName: !Ref S3Bucket CleanupS3BucketOnDelete: Type: Custom::cleanups3bucket Properties: ServiceToken: !GetAtt CleanupResourcesOnDeletion.Arn BucketName: !Ref S3BucketForJobOutput DeattachIamPolicy: Type: Custom::deatchpolicy Properties: ServiceToken: !GetAtt CleanupResourcesOnDeletion.Arn IamRole: !Ref EC2Role KMSKey: Type: 'AWS::KMS::Key' Properties: EnableKeyRotation: true KeyPolicy: Version: '2012-10-17' Statement: - Effect: Allow Principal: AWS: !Sub 'arn:aws:iam::${AWS::AccountId}:root' Action: 'kms:*' Resource: '*' - Effect: Allow Principal: AWS: '*' Action: - kms:Decrypt - kms:ReEncrypt* - kms:GenerateDataKey* - kms:CreateGrant - kms:DescribeKey Resource: '*' Condition: StringEquals: kms:CallerAccount: !Ref AWS::AccountId kms:ViaService: !Sub 'secretsmanager.${AWS::Region}.amazonaws.com' KeyAlias: Type: 'AWS::KMS::Alias' Properties: AliasName: !Sub 'alias/${AWS::StackName}' TargetKeyId: !Ref KMSKey MySecretA: Type: 'AWS::SecretsManager::Secret' Properties: Name: !Sub 'AmazonMSK_${AWS::StackName}' Description: "This secret has a dynamically generated secret password." GenerateSecretString: SecretStringTemplate: '{"username": "test-user"}' GenerateStringKey: "password" PasswordLength: 10 ExcludeCharacters: '"/\' KmsKeyId: !GetAtt KMSKey.Arn Tags: - Key: AppName Value: !Ref AWS::StackName MSKClientMachineSG: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: "Security group attached to attach mks client machine" GroupName: "msk-client-machine" VpcId: !Ref VPC SecurityGroupIngress: - IpProtocol: tcp FromPort: 22 ToPort: 22 CidrIp: !Ref SshAllowedCidr Description: "Allow SSH connection" SecurityGroupEgress: - Description: Allow all outbound traffic IpProtocol: "-1" CidrIp: 0.0.0.0/0 Tags: - Key: AppName Value: !Ref AWS::StackName EC2Role: Type: AWS::IAM::Role Properties: RoleName: !Sub "EC2-Instance-Role-${EnvironmentName}" AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: - ec2.amazonaws.com Action: - sts:AssumeRole Path: '/' ManagedPolicyArns: - "arn:aws:iam::aws:policy/AWSGlueSchemaRegistryReadonlyAccess" Policies: - PolicyName: !Sub "ec2-secretm-access-policy-${EnvironmentName}" PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - secretsmanager:GetSecretValue Resource: !Ref MySecretA - Effect: Allow Action: - s3:ListBucket - s3:*Object Resource: - !GetAtt S3Bucket.Arn - !Join ["/", [!GetAtt S3Bucket.Arn, "*"]] EC2InstanceProfile: Type: AWS::IAM::InstanceProfile Properties: InstanceProfileName: !Sub "EC2InstanceProfile-${EnvironmentName}" Roles: - !Ref EC2Role KafkaClientEC2Instance: Type: AWS::EC2::Instance Properties: InstanceType: !Ref InstanceType KeyName: !Ref KeyName IamInstanceProfile: !Ref EC2InstanceProfile SubnetId: !Ref PublicSubnet1 ImageId: Fn::FindInMap: - "RegionAMI" - Ref: "AWS::Region" - "AMI" SecurityGroupIds: - !Ref MSKClientMachineSG - !Ref SecurityGroup Tags: - Key: AppName Value: !Ref AWS::StackName UserData: Fn::Base64: !Sub | #!/bin/bash yum install java-1.8.0-devel -y /usr/sbin/alternatives --config java /usr/sbin/alternatives --config javac wget https://repos.fedorapeople.org/repos/dchen/apache-maven/epel-apache-maven.repo -O /etc/yum.repos.d/epel-apache-maven.repo sed -i s/\$releasever/6/g /etc/yum.repos.d/epel-apache-maven.repo yum install -y apache-maven Outputs: VPC: Description: A reference to the created VPC Value: !Ref VPC PublicSubnet1: Description: A reference to the public subnet in the 1st Availability Zone Value: !Ref PublicSubnet1 PublicSubnet2: Description: A reference to the public subnet in the 2nd Availability Zone Value: !Ref PublicSubnet2 PrivateSubnet1: Description: A reference to the private subnet in the 1st Availability Zone Value: !Ref PrivateSubnet1 PrivateSubnet2: Description: A reference to the private subnet in the 2nd Availability Zone Value: !Ref PrivateSubnet2 SecurityGroup: Description: Security group with self-referencing inbound rule Value: !Ref SecurityGroup BucketNameForScript: Value: !Ref S3Bucket Description: Name of the sample Amazon S3 bucket for Glue job. SecretArn: Description: Secret arn for MSK authentication Value: !Ref MySecretA KMSKeyId: Description: KMS key arn for MSK secret encryption Value: !Ref KMSKey InstanceId: Description: InstanceId of the newly created EC2 instance Value: !Ref 'KafkaClientEC2Instance' PublicDNS: Description: Public DNSName of the newly created EC2 instance Value: !GetAtt [KafkaClientEC2Instance, PublicDnsName] SecurityGroupIdForGlueConnection: Value: !Ref SecurityGroup Description: Security group id for Glue connection. S3BucketForOutput: Value: !Ref S3BucketForJobOutput Description: S3 bucket name for Glue job output