|
|
@@ -0,0 +1,366 @@ |
|
|
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\\ContabBootstrapScript.ps1", "./ContabBootstrapScript.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\\ContabBootstrapScript.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 contabmysqlinstance --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); |
|
|
|
|
|
//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 |
|
|
} |
|
|
} |