Last active
October 30, 2024 09:14
-
-
Save kritro/f0b9e3d7aa932ece8ff4ce5c9191fd74 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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