Skip to content

Instantly share code, notes, and snippets.

@kritro
Last active October 30, 2024 09:14
Show Gist options
  • Save kritro/f0b9e3d7aa932ece8ff4ce5c9191fd74 to your computer and use it in GitHub Desktop.
Save kritro/f0b9e3d7aa932ece8ff4ce5c9191fd74 to your computer and use it in GitHub Desktop.
import * as cdk from '@aws-cdk/core';
import * as ec2 from '@aws-cdk/aws-ec2'; // import ec2 library
import * as iam from '@aws-cdk/aws-iam';
import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2';
import * as elbvtargets from '@aws-cdk/aws-elasticloadbalancingv2-targets';
import * as ssm from '@aws-cdk/aws-ssm';
import * as kms from '@aws-cdk/aws-kms';
export class InfraCdkappMysqlStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
//Variables
const volumeEncryptionKey = ssm.StringParameter.valueForStringParameter(this, '/acme/cdk/ec2/VolumeKmsKeyId');
var servername = ssm.StringParameter.valueForStringParameter(this, '/acme/cdk/ec2/app-mysql/servername');
const environment = ssm.StringParameter.valueForStringParameter(this, '/acme/cdk/ec2/app-mysql/environment');
const serverkey = ssm.StringParameter.valueForStringParameter(this, '/acme/cdk/ec2/app-mysql/serverkey');
const serverdescription = ssm.StringParameter.valueForStringParameter(this, '/acme/cdk/ec2/app-mysql/serverdescription');
var instancetypeparam = ssm.StringParameter.valueForStringParameter(this, '/acme/cdk/ec2/app-mysql/instancetype');
const domainJoinDocument = ssm.StringParameter.valueForStringParameter(this, '/acme/cdk/ec2/app-mysql/domainJoinDocument');
const directoryID = ssm.StringParameter.valueForStringParameter(this, '/acme/cdk/ec2/app-mysql/directoryId');
var volumeDsize = 100;
var volumeCsize = 50;
const application = "appMysql";
const createdBy = "[email protected]";
const os = "Windows";
const legalEntitiy = "Acme AS";
const country = "Country";
//Set default value to be t3.medium
var instancetype = ec2.InstanceType.of(
ec2.InstanceClass.T3,
ec2.InstanceSize.MEDIUM
);
//Check the instancetypeparam from ssm param store and change the instance type accordingly
if (instancetypeparam == "m5.large") {
instancetype = ec2.InstanceType.of(
ec2.InstanceClass.M5,
ec2.InstanceSize.LARGE
)
}
//Create vpc object
const vpc = ec2.Vpc.fromLookup(this, 'VPC', {
vpcName: 'acme-standard-vpc'
});
// var subnet1Id = cdk.Fn.importValue('acme-standard-vpc-private-sn-1');
var subnet1Id = "subnet-1237385573857398";
const subnet = vpc.selectSubnets({
subnets: [
ec2.Subnet.fromSubnetAttributes(this, 'subnet1', {
subnetId: subnet1Id,
availabilityZone: 'eu-west-1a'
})
]
});
var publicSubnet1Id = "subnet-98765432123456";
const publicSubnet = vpc.selectSubnets({
subnets: [
ec2.Subnet.fromSubnetAttributes(this, 'publicSubnet1', {
subnetId: publicSubnet1Id,
availabilityZone: 'eu-west-1a'
})
]
});
//Network load balancer
const nlb = new elbv2.NetworkLoadBalancer(this, 'NLB', {
vpc,
internetFacing: true,
vpcSubnets: publicSubnet,
loadBalancerName: "app-mysql-nlb",
crossZoneEnabled: false
});
cdk.Tags.of(nlb).add("Name", servername + "-network-load-balancer");
cdk.Tags.of(nlb).add("Application", application);
cdk.Tags.of(nlb).add("Description", "network-load-balancer-for-" + serverdescription);
const role = new iam.Role(this, 'app-mysql-server-role', {
assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com')
});
role.addManagedPolicy(iam.ManagedPolicy.fromManagedPolicyArn(this, "AmazonSSMAutomationRole", "arn:aws:iam::aws:policy/service-role/AmazonSSMAutomationRole"));
role.addManagedPolicy(iam.ManagedPolicy.fromManagedPolicyArn(this, "AmazonSSMManagedInstanceCore", "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"));
const appPolicy = {
"Version": "2012-10-17",
"Statement": [
{
"Sid": "appIamInstancePolicy",
"Effect": "Allow",
"Action": [
"ec2:DescribeInstances",
"ssm:ListTagsForResource",
"ds:CreateComputer"
],
"Resource": "*"
},
{
"Sid": "AttachVolume",
"Effect": "Allow",
"Action": [
"ec2:AttachVolume"
],
"Resource": "*"
},
{
"Sid": "AssumeKMSRole",
"Effect": "Allow",
"Action": [
"sts:AssumeRole"
],
"Resource": "arn:aws:iam::2222222222222:role/*"
},
{
"Sid": "GetKMSKey",
"Effect": "Allow",
"Action": [
"kms:Encrypt",
"kms:Decrypt",
"kms:ReEncrypt*",
"kms:GenerateDataKey*",
"kms:DescribeKey",
"kms:*"
],
"Resource": "*"
},
{
"Sid": "GetSecretValue",
"Effect": "Allow",
"Action": "secretsmanager:GetSecretValue",
"Resource": [
"arn:aws:secretsmanager:eu-west-1:111111111111:secret:domainJoinUserAD-test",
"arn:aws:secretsmanager:eu-west-1:333333333333:secret:domainJoinUserAD-stage",
"arn:aws:secretsmanager:eu-west-1:666666666666:secret:domainJoinUserAD-prod",
"arn:aws:secretsmanager:eu-west-1:111111111111:secret:DefaultPasswordForWindowsServer-test",
"arn:aws:secretsmanager:eu-west-1:333333333333:secret:DefaultPasswordForWindowsServer-stage",
"arn:aws:secretsmanager:eu-west-1:444444444444:secret:DefaultPasswordForWindowsServer-prod"
]
},
{
"Sid": "DomainJoinComputer",
"Effect": "Allow",
"Action": [
"ds:CreateComputer"
],
"Resource": [
"arn:aws:ds:eu-west-1:" + process.env.account + ":directory/" + directoryID
],
}
]
};
const appPolicyDocument = iam.PolicyDocument.fromJson(appPolicy);
var appInstancePolicy = new iam.Policy(this, 'app-instance-policy', {
policyName: "appIamInstancePolicy",
document: appPolicyDocument
});
appInstancePolicy.attachToRole(role);
// lets create a security group for our instance
// A security group acts as a virtual firewall for your instance to control inbound and outbound traffic.
const securityGroup = new ec2.SecurityGroup(this, 'app-mysql-server-sg',
{
vpc: vpc,
allowAllOutbound: true, // will let your instance send outboud traffic
securityGroupName: 'app-mysql-server-sg',
}
)
cdk.Tags.of(securityGroup).add("Name", servername + "-security-group");
cdk.Tags.of(securityGroup).add("Application", application);
cdk.Tags.of(securityGroup).add("Description", "security-group-for-" + serverdescription);
// lets use the security group to allow inbound traffic on specific ports from different sources
//allow mysql from a larger subnet cidr
securityGroup.addIngressRule(
ec2.Peer.ipv4("172.10.0.0/20"),
ec2.Port.tcp(3306),
'Allows mysql access from AWSCIDR'
)
//allow rdp from specific ip, for instance jumpbox
securityGroup.addIngressRule(
ec2.Peer.ipv4("172.10.0.55/32"),
ec2.Port.tcp(3389),
'Allows rdp access from jumpbox based on ip'
)
//allow rdp from another instance based on its security group id
securityGroup.addIngressRule(ec2.SecurityGroup.fromSecurityGroupId(this, "jumpboxSG", "sg-12345678987654"), ec2.Port.tcp(3389), 'allow rdp access based on the jumpboxs security group id');
//get the public ip of the actuall machine you are doing this deployment from and allow rdp and mysql from that using the public-ip packet
const publicIp = require('public-ip');
async function waitForIP() {
var ip = await publicIp.v4();
// console.log(ip);
return ip;
}
function setLaptopIngressRule() {
waitForIP().then(result => securityGroup.addIngressRule(ec2.Peer.ipv4(result+"/32"), ec2.Port.tcp(3389), 'Allows rdp access from laptop public'));
}
function setLaptopIngressRuleMysql() {
waitForIP().then(result => securityGroup.addIngressRule(ec2.Peer.ipv4(result+"/32"), ec2.Port.tcp(3306), 'Allows mysql access from laptop public'));
}
//initialize the functions that creates the ingress rules
setLaptopIngressRule();
setLaptopIngressRuleMysql();
//Get the correct ami from your region for the Windows 2019 base server and put the region and ami id here
const windows = ec2.MachineImage.latestWindows(ec2.WindowsVersion.WINDOWS_SERVER_2019_ENGLISH_FULL_BASE);
const genericWindows = ec2.MachineImage.genericWindows({
'eu-west-1': 'ami-87776hhg65f5'
});
//define encrypted volume that would be used as D drive
const dVolume = new ec2.Volume(this, 'DataDisk', {
availabilityZone: 'eu-west-1a',
size: cdk.Size.gibibytes(volumeDsize),
encrypted: true,
volumeType: ec2.EbsDeviceVolumeType.GP3,
encryptionKey: kms.Key.fromKeyArn(this, 'VolumeEncryptionKey', volumeEncryptionKey),
});
//apply tags to the volume
cdk.Tags.of(dVolume).add("Name", servername + "-d-volume");
cdk.Tags.of(dVolume).add("Application", application);
cdk.Tags.of(dVolume).add("Description", "d-volume-for-" + serverdescription);
cdk.Tags.of(dVolume).add("CreatedBy", createdBy);
//if extra custom userdata is needed set them here and uncommend the userdata on the instance
const userdataScript = "<persist>true</persist>";
const userData = ec2.UserData.forWindows();
userData.addCommands(userdataScript);
//set device name for the volume that would be used as D drive
const targetDevice = '/dev/sdb';
//define the initData that will be used when creating the instance, this describes all the commands that will be ran on the instance
const initData = ec2.CloudFormationInit.fromElements(
// ec2.InitFile.fromUrl("c:\\cfn\\mysql.msi", "https://downloads.mysql.com/archives/get/p/25/file/mysql-installer-community-8.0.24.0.msi"),
ec2.InitFile.fromAsset("c:\\cfn\\BootstrapScript.ps1", "./BootstrapScript.ps1"),
ec2.InitFile.fromAsset("c:\\cfn\\CreateADGroups.ps1", "./CreateADGroups.ps1"),
ec2.InitPackage.msi("https://s3.amazonaws.com/aws-cli/AWSCLI64.msi"),
ec2.InitCommand.shellCommand('powershell.exe [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString(\'https://chocolatey.org/install.ps1\'))', { key: "1-InstallChoco", waitAfterCompletion: ec2.InitCommandWaitDuration.of(cdk.Duration.seconds(5)) }),
ec2.InitCommand.shellCommand("powershell.exe -Command Rename-Computer (Get-EC2Tag -Filter @{Name='resource-id'; Values=(Invoke-WebRequest http://169.254.169.254/latest/meta-data/instance-id -UseBasicParsing).Content}).Where({$_.Key -eq 'Name'}).Value", { key: "2-RenameComputer", waitAfterCompletion: ec2.InitCommandWaitDuration.of(cdk.Duration.seconds(5)) }),
ec2.InitCommand.shellCommand('powershell.exe -Command Restart-Service AmazonSSMAgent"', { key: "3-RestartSSMAgent", waitAfterCompletion: ec2.InitCommandWaitDuration.of(cdk.Duration.seconds(5)) }),
ec2.InitCommand.shellCommand('powershell.exe Add-EC2Volume -InstanceId (Invoke-WebRequest http://169.254.169.254/latest/meta-data/instance-id -UseBasicParsing).Content -VolumeId ' + dVolume.volumeId + ' -Device ' + targetDevice + ' -Region ' + process.env.CDK_DEFAULT_REGION, { key: "4-AttachVolume", waitAfterCompletion: ec2.InitCommandWaitDuration.of(cdk.Duration.seconds(60)) }),
ec2.InitCommand.shellCommand('powershell.exe -File "c:\\cfn\\BootstrapScript.ps1"', { key: "5-install", waitAfterCompletion: ec2.InitCommandWaitDuration.of(cdk.Duration.seconds(10)) }),
ec2.InitCommand.shellCommand('powershell.exe -Command Send-SSMCommand -InstanceId (Invoke-WebRequest http://169.254.169.254/latest/meta-data/instance-id -UseBasicParsing).Content -DocumentName ' + domainJoinDocument + ' -TimeoutSecond 600 -Region ' + process.env.CDK_DEFAULT_REGION, { key: "6-DomainJoinServer", waitAfterCompletion: ec2.InitCommandWaitDuration.of(cdk.Duration.seconds(60)) }),
ec2.InitCommand.shellCommand('powershell.exe -Command Restart-Computer -force', { key: "6-Restart", waitAfterCompletion: ec2.InitCommandWaitDuration.forever() }),
// // ec2.InitCommand.shellCommand('powershell.exe -File "c:\\cfn\\CreateADGroups.ps1"', { key: "7-CreateADGroups", waitAfterCompletion: ec2.InitCommandWaitDuration.of(cdk.Duration.seconds(5)) }),
ec2.InitCommand.shellCommand('powershell.exe -Command Set-NetFirewallProfile -Profile Domain,Public,Private -Enabled False', { key: "7-DisableFirewall", waitAfterCompletion: ec2.InitCommandWaitDuration.of(cdk.Duration.seconds(10)) }),
ec2.InitCommand.shellCommand('powershell.exe -File C:\\ProgramData\\Amazon\\EC2-Windows\\Launch\\Scripts\\InitializeDisks.ps1', { key: "8-Initialize", waitAfterCompletion: ec2.InitCommandWaitDuration.of(cdk.Duration.seconds(5)) }),
// ec2.InitCommand.shellCommand('msiexec /q /log c:\\cfn\\log\\mysql-install.log /i "C:\\cfn\\mysql.msi"', { key: "91-installMysql", waitAfterCompletion: ec2.InitCommandWaitDuration.of(cdk.Duration.seconds(30)) }),
ec2.InitCommand.shellCommand('powershell.exe choco install mysql -y', { key: "9-installMysql", waitAfterCompletion: ec2.InitCommandWaitDuration.of(cdk.Duration.seconds(30)) }),
ec2.InitCommand.shellCommand('cfn-signal.exe -e %ERRORLEVEL% --resource appmysqlserver --stack ' + this.stackId + ' --region ' + this.region, { key: "91-Signal", waitAfterCompletion: ec2.InitCommandWaitDuration.of(cdk.Duration.seconds(5)) })
)
//create a boolean variable to be able to deploy and undeploy the instance for troubleshooting purposes
var deployInstance = false;
var instance: ec2.Instance;
if (deployInstance) {
// Finally lets provision our ec2 instance
instance = new ec2.Instance(this, "app-mysql-server-instance", {
vpc: vpc,
role: role,
securityGroup: securityGroup,
instanceName: servername,
instanceType: instancetype,
blockDevices: [
{
deviceName: '/dev/sda1',
volume: ec2.BlockDeviceVolume.ebs(volumeCsize),
}
],
machineImage: genericWindows,
vpcSubnets: subnet,
// userData: userData, //userdata is in this case not passed along as we dont need the persist tag since cfn-signal is done via init command
// privateIpAddress: privateIp,
init: initData,
initOptions: {
timeout: cdk.Duration.minutes(35),
ignoreFailures: false,
},
keyName: serverkey, // we will create this in the console before we deploy
})
//allow for the volume to be attached to the instance
dVolume.grantAttachVolume(instance);
//override the logicalid
instance.instance.overrideLogicalId('appmysqlserver');
//apply tags to the volume
cdk.Tags.of(instance).add("Name", servername);
cdk.Tags.of(instance).add("Application", application);
cdk.Tags.of(instance).add("Description", serverdescription);
cdk.Tags.of(instance).add("CreatedBy", createdBy);
cdk.Tags.of(instance).add("Environment", environment);
cdk.Tags.of(instance).add("OS", os);
cdk.Tags.of(instance).add("Country", country);
cdk.Tags.of(instance).add("Legal Entity", legalEntitiy);
// cdk.Tags.of(instance).add("", );
// Add a listener on a mysql port.
const listener = nlb.addListener('Listener', {
port: 3306,
protocol: elbv2.Protocol.TCP
});
//apply tags to the listender
cdk.Tags.of(listener).add("Name", servername + "-mysql-listener");
cdk.Tags.of(listener).add("Application", application);
cdk.Tags.of(listener).add("Description", "listener-for-" + serverdescription);
//add target to the listender, the target is the instance on the mysql port
listener.addTargets('app-instance-mysql-target', {
port: 3306,
targets: [new elbvtargets.InstanceIdTarget(instance.instanceId)]
})
//add a listener on the rdp port
const rdpListener = nlb.addListener('RDP-listender', {
port: 3389,
protocol: elbv2.Protocol.TCP
});
//apply tags to the listener
cdk.Tags.of(rdpListener).add("Name", servername + "-rdp-listener");
cdk.Tags.of(rdpListener).add("Application", application);
cdk.Tags.of(rdpListener).add("Description", "listener-for-" + serverdescription);
//add rdp target to the listener, the target is the instance on the rdp port
rdpListener.addTargets('app-instance-rdp-target', {
port: 3389,
targets: [new elbvtargets.InstanceIdTarget(instance.instanceId)]
})
new cdk.CfnOutput(this, 'app-nlb-dns-name', {
value: nlb.loadBalancerDnsName
})
new cdk.CfnOutput(this, 'app-mysql-private-ip', {
value: instance.instancePrivateIp
})
}//end if deployInstance
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment