diff --git a/reference-artifacts/SAMPLE_CONFIGS/config.example-oldIP.json b/reference-artifacts/SAMPLE_CONFIGS/config.example-oldIP.json index 934b52401..c97ab9d5c 100644 --- a/reference-artifacts/SAMPLE_CONFIGS/config.example-oldIP.json +++ b/reference-artifacts/SAMPLE_CONFIGS/config.example-oldIP.json @@ -629,7 +629,7 @@ "name": "EC2-INSTANCE-PROFILE", "type": "custom", "resource-types": ["AWS::EC2::Instance"], - "runtime": "nodejs14.x", + "runtime": "nodejs16.x", "remediation-action": "Attach-IAM-Instance-Profile", "remediation": true, "remediation-params": { @@ -641,7 +641,7 @@ "name": "EC2-INSTANCE-PROFILE-PERMISSIONS", "type": "custom", "resource-types": ["AWS::IAM::Role"], - "runtime": "nodejs14.x", + "runtime": "nodejs16.x", "parameters": { "AWSManagedPolicies": "AmazonSSMManagedInstanceCore, AmazonSSMDirectoryServiceAccess, CloudWatchAgentServerPolicy", "CustomerManagedPolicies": "${SEA::EC2InstaceProfilePermissions}", diff --git a/src/deployments/cdk/README.md b/src/deployments/cdk/README.md index ee3997444..68228f0c7 100644 --- a/src/deployments/cdk/README.md +++ b/src/deployments/cdk/README.md @@ -108,7 +108,6 @@ from secrets manager in your master account. }, ] - Now that we have created all the files, we can start testing the deployment. Run the following command to synthesize the CloudFormation template from CDK. diff --git a/src/deployments/cdk/cdk.sh b/src/deployments/cdk/cdk.sh index 370c79905..4b3a56c6e 100755 --- a/src/deployments/cdk/cdk.sh +++ b/src/deployments/cdk/cdk.sh @@ -2,6 +2,6 @@ export CONFIG_MODE="development" export CDK_PLUGIN_ASSUME_ROLE_NAME="ASEA-PipelineRole" -export AWS_REGION="us-east-1" +export AWS_REGION="ca-central-1" pnpx ts-node --transpile-only cdk.ts $@ \ No newline at end of file diff --git a/src/deployments/cdk/src/common/ad-users-groups.ts b/src/deployments/cdk/src/common/ad-users-groups.ts index 8ca78e7ea..f66fe5ddb 100644 --- a/src/deployments/cdk/src/common/ad-users-groups.ts +++ b/src/deployments/cdk/src/common/ad-users-groups.ts @@ -123,6 +123,40 @@ export class ADUsersAndGroups extends Construct { const stack = AcceleratorStack.of(this); const prefix = trimSpecialCharacters(stack.acceleratorPrefix); + const launchTemplate = new cdk.aws_ec2.CfnLaunchTemplate(this, 'RDGWLaunchTemplate', { + launchTemplateName: `${prefix}-RDGWLaunchTemplate`, + launchTemplateData: { + blockDeviceMappings: [ + { + deviceName: '/dev/sda1', + ebs: { + volumeSize: 50, + volumeType: 'gp2', + encrypted: true, + }, + }, + ], + // securityGroupIds: [securityGroup.securityGroups[0].id], + imageId: latestRdgwAmiId, + iamInstanceProfile: { + name: createIamInstanceProfileName(madDeploymentConfig['rdgw-instance-role']), + }, + networkInterfaces: [ + { + deviceIndex: 0, + associatePublicIpAddress: false, + + groups: [securityGroup.securityGroups[0].id], + }, + ], + instanceType: madDeploymentConfig['rdgw-instance-type'], + keyName: keyPairName, + metadataOptions: { + httpTokens: 'required', + httpEndpoint: 'enabled', + }, + }, + }); const launchConfig = new LaunchConfiguration(this, 'RDGWLaunchConfiguration', { launchConfigurationName: `${prefix}-RDGWLaunchConfiguration`, @@ -150,7 +184,11 @@ export class ADUsersAndGroups extends Construct { const autoScalingGroupSize = madDeploymentConfig['num-rdgw-hosts']; const autoscalingGroup = new CfnAutoScalingGroup(this, 'RDGWAutoScalingGroupB', { autoScalingGroupName: `${prefix}-RDGWAutoScalingGroup`, - launchConfigurationName: launchConfig.ref, + // launchConfigurationName: launchConfig.ref, + launchTemplate: { + version: '1', + launchTemplateId: launchTemplate.ref, + }, vpcZoneIdentifier: subnetIds, maxInstanceLifetime: madDeploymentConfig['rdgw-max-instance-age'] * 86400, minSize: `${madDeploymentConfig['min-rdgw-hosts']}`, @@ -174,6 +212,166 @@ export class ADUsersAndGroups extends Construct { }, }; + launchTemplate.addPropertyOverride( + 'LaunchTemplateData.UserData', + cdk.Fn.base64( + `\n`, + ), + ); + + launchTemplate.addOverride('Metadata.AWS::CloudFormation::Init', { + configSets: { + config: ['setup', 'join', 'installRDS', 'createADConnectorUser', 'configurePasswordPolicy', 'finalize'], + }, + setup: { + files: { + 'c:\\cfn\\cfn-hup.conf': { + content: `[main]\n stack=${stackName}\n region=${cdk.Aws.REGION}\n`, + }, + 'c:\\cfn\\hooks.d\\cfn-auto-reloader.conf': { + content: `[cfn-auto-reloader-hook]\n triggers=post.update\n path=Resources.${launchTemplate.logicalId}.Metadata.AWS::CloudFormation::Init\n action=cfn-init.exe -v -c config -s ${stackId} -r ${launchTemplate.logicalId} --region ${cdk.Aws.REGION}\n`, + }, + 'C:\\Windows\\system32\\WindowsPowerShell\\v1.0\\Modules\\AWSQuickStart\\AWSQuickStart.psm1': { + source: `https://${s3BucketName}.s3.${cdk.Aws.REGION}.amazonaws.com/${s3KeyPrefix}AWSQuickStart.psm1`, + authentication: 'S3AccessCreds', + }, + 'C:\\cfn\\scripts\\Join-Domain.ps1': { + source: `https://${s3BucketName}.s3.${cdk.Aws.REGION}.amazonaws.com/${s3KeyPrefix}Join-Domain.ps1`, + authentication: 'S3AccessCreds', + }, + 'c:\\cfn\\scripts\\Initialize-RDGW.ps1': { + source: `https://${s3BucketName}.s3.${cdk.Aws.REGION}.amazonaws.com/${s3KeyPrefix}Initialize-RDGW.ps1`, + authentication: 'S3AccessCreds', + }, + 'c:\\cfn\\scripts\\AD-user-setup.ps1': { + source: `https://${s3BucketName}.s3.${cdk.Aws.REGION}.amazonaws.com/${s3KeyPrefix}AD-user-setup.ps1`, + authentication: 'S3AccessCreds', + }, + 'c:\\cfn\\scripts\\AD-group-setup.ps1': { + source: `https://${s3BucketName}.s3.${cdk.Aws.REGION}.amazonaws.com/${s3KeyPrefix}AD-group-setup.ps1`, + authentication: 'S3AccessCreds', + }, + 'c:\\cfn\\scripts\\AD-user-group-setup.ps1': { + source: `https://${s3BucketName}.s3.${cdk.Aws.REGION}.amazonaws.com/${s3KeyPrefix}AD-user-group-setup.ps1`, + authentication: 'S3AccessCreds', + }, + 'c:\\cfn\\scripts\\AD-group-grant-permissions-setup.ps1': { + source: `https://${s3BucketName}.s3.${cdk.Aws.REGION}.amazonaws.com/${s3KeyPrefix}AD-group-grant-permissions-setup.ps1`, + authentication: 'S3AccessCreds', + }, + 'c:\\cfn\\scripts\\AD-connector-permissions-setup.ps1': { + source: `https://${s3BucketName}.s3.${cdk.Aws.REGION}.amazonaws.com/${s3KeyPrefix}AD-connector-permissions-setup.ps1`, + authentication: 'S3AccessCreds', + }, + 'c:\\cfn\\scripts\\Configure-password-policy.ps1': { + source: `https://${s3BucketName}.s3.${cdk.Aws.REGION}.amazonaws.com/${s3KeyPrefix}Configure-password-policy.ps1`, + authentication: 'S3AccessCreds', + }, + }, + services: { + windows: { + 'cfn-hup': { + enabled: 'true', + ensureRunning: 'true', + files: ['c:\\cfn\\cfn-hup.conf', 'c:\\cfn\\hooks.d\\cfn-auto-reloader.conf'], + }, + }, + }, + commands: { + 'a-set-execution-policy': { + command: 'powershell.exe -Command "Set-ExecutionPolicy RemoteSigned -Force"', + waitAfterCompletion: '0', + }, + 'b-init-quickstart-module': { + command: `powershell.exe -Command "New-AWSQuickStartResourceSignal -Stack ${props.stackName} -Resource ${autoscalingGroup.logicalId} -Region ${cdk.Aws.REGION}"`, + waitAfterCompletion: '0', + }, + }, + }, + join: { + commands: { + 'a-join-domain': { + command: `powershell.exe -Command "C:\\cfn\\scripts\\Join-Domain.ps1 -DomainName ${madDeploymentConfig['dns-domain']} -UserName ${madDeploymentConfig['netbios-domain']}\\admin -Password ((Get-SECSecretValue -SecretId ${adminPasswordArn}).SecretString)"`, + waitAfterCompletion: 'forever', + }, + }, + }, + installRDS: { + commands: { + 'a-install-rds': { + command: 'powershell.exe -Command "Install-WindowsFeature RDS-Gateway,RSAT-RDS-Gateway,RSAT-AD-Tools"', + waitAfterCompletion: '0', + }, + 'b-configure-rdgw': { + command: `powershell.exe -ExecutionPolicy RemoteSigned C:\\cfn\\scripts\\Initialize-RDGW.ps1 -ServerFQDN $($env:COMPUTERNAME + '.${madDeploymentConfig['dns-domain']}') -DomainNetBiosName ${madDeploymentConfig['netbios-domain']} -GroupName 'domain admins'`, + waitAfterCompletion: '0', + }, + }, + }, + createADConnectorUser: { + commands: { + 'a-create-ad-users': { + command: `powershell.exe -ExecutionPolicy RemoteSigned ${adUsersCommand.join('; ')}`, + waitAfterCompletion: '0', + }, + 'b-create-ad-groups': { + command: `powershell.exe -ExecutionPolicy RemoteSigned C:\\cfn\\scripts\\AD-group-setup.ps1 -GroupNames \'${adGroups.join( + ',', + )}\' -DomainAdminUser ${ + madDeploymentConfig['netbios-domain'] + }\\admin -DomainAdminPassword ((Get-SECSecretValue -SecretId ${adminPasswordArn}).SecretString)`, + waitAfterCompletion: '0', + }, + 'c-configure-ad-users-groups': { + command: `powershell.exe -ExecutionPolicy RemoteSigned ${adUserGroupsCommand.join('; ')}`, + waitAfterCompletion: '0', + }, + 'd-configure-ad-group-permissions': { + command: `powershell.exe -ExecutionPolicy RemoteSigned C:\\cfn\\scripts\\AD-connector-permissions-setup.ps1 -GroupName ${madDeploymentConfig['adc-group']} -DomainAdminUser ${madDeploymentConfig['netbios-domain']}\\admin -DomainAdminPassword ((Get-SECSecretValue -SecretId ${adminPasswordArn}).SecretString)`, + waitAfterCompletion: '0', + }, + }, + }, + configurePasswordPolicy: { + commands: { + 'a-set-password-policy': { + command: `powershell.exe -ExecutionPolicy RemoteSigned C:\\cfn\\scripts\\Configure-password-policy.ps1 -DomainAdminUser admin -DomainAdminPassword ((Get-SECSecretValue -SecretId ${adminPasswordArn}).SecretString) -ComplexityEnabled:$${pascalCase( + String(madDeploymentConfig['password-policies'].complexity), + )} -LockoutDuration 00:${ + madDeploymentConfig['password-policies']['lockout-duration'] + }:00 -LockoutObservationWindow 00:${ + madDeploymentConfig['password-policies']['lockout-attempts-reset'] + }:00 -LockoutThreshold ${madDeploymentConfig['password-policies']['failed-attempts']} -MaxPasswordAge:${ + madDeploymentConfig['password-policies']['max-age'] + }.00:00:00 -MinPasswordAge:${ + madDeploymentConfig['password-policies']['min-age'] + }.00:00:00 -MinPasswordLength:${ + madDeploymentConfig['password-policies']['min-len'] + } -PasswordHistoryCount:${madDeploymentConfig['password-policies'].history} -ReversibleEncryptionEnabled:$${ + madDeploymentConfig['password-policies'].reversible + }`, + waitAfterCompletion: '0', + }, + }, + }, + finalize: { + commands: { + '1-signal-success': { + command: 'powershell.exe -Command "Write-AWSQuickStartStatus"', + waitAfterCompletion: '0', + }, + }, + }, + }); + + launchTemplate.addOverride('Metadata.AWS::CloudFormation::Authentication', { + S3AccessCreds: { + type: 'S3', + roleName: madDeploymentConfig['rdgw-instance-role'], + buckets: [s3BucketName], + }, + }); + launchConfig.addOverride('Metadata.AWS::CloudFormation::Authentication', { S3AccessCreds: { type: 'S3', @@ -185,7 +383,6 @@ export class ADUsersAndGroups extends Construct { launchConfig.userData = cdk.Fn.base64( `\n`, ); - launchConfig.addOverride('Metadata.AWS::CloudFormation::Init', { configSets: { config: ['setup', 'join', 'installRDS', 'createADConnectorUser', 'configurePasswordPolicy', 'finalize'], diff --git a/src/deployments/cdk/src/deployments/firewall/cluster/step-3.ts b/src/deployments/cdk/src/deployments/firewall/cluster/step-3.ts index 105f4d72d..388bb3815 100644 --- a/src/deployments/cdk/src/deployments/firewall/cluster/step-3.ts +++ b/src/deployments/cdk/src/deployments/firewall/cluster/step-3.ts @@ -108,7 +108,7 @@ export async function step3(props: FirewallStep3Props) { continue; } - // TODO add region check also if vpc name is not unique accross Account + // TODO add region check also if vpc name is not unique across Account const vpcConfig = vpcConfigs.find(v => v.vpcConfig.name === firewallConfig.vpc)?.vpcConfig; if (!vpcConfig) { console.log(`Skipping firewall deployment because of missing VPC config "${firewallConfig.vpc}"`); diff --git a/src/deployments/cdk/src/deployments/firewall/cluster/step-4.ts b/src/deployments/cdk/src/deployments/firewall/cluster/step-4.ts index 6b7ed16b7..903d3f7e3 100644 --- a/src/deployments/cdk/src/deployments/firewall/cluster/step-4.ts +++ b/src/deployments/cdk/src/deployments/firewall/cluster/step-4.ts @@ -183,6 +183,12 @@ async function createFirewallCluster(props: { name: `${firewallName}`, suffixLength: 0, }); + + const launchTemplateName = createName({ + name: `${firewallName}lt`, + suffixLength: 0, + }); + const blockDeviceMappings = deviceNames.map(deviceName => ({ deviceName, ebs: { @@ -192,6 +198,33 @@ async function createFirewallCluster(props: { }, })); + const launchTemplate = new cdk.aws_ec2.CfnLaunchTemplate(accountStack, `FirewallLaunchTemplate-${firewallName}`, { + launchTemplateName, + launchTemplateData: { + blockDeviceMappings, + // securityGroupIds: [securityGroup.securityGroups[0].id], + imageId, + iamInstanceProfile: { + name: instanceRoleName ? createIamInstanceProfileName(instanceRoleName) : undefined, + }, + networkInterfaces: [ + { + deviceIndex: 0, + associatePublicIpAddress, + + groups: [securityGroup.id], + }, + ], + instanceType, + keyName, + metadataOptions: { + httpTokens: 'required', + httpEndpoint: 'enabled', + }, + userData: userData ? cdk.Fn.base64(userData) : undefined, + }, + }); + // Create LaunchConfiguration const launchConfig = new LaunchConfiguration(accountStack, `FirewallLaunchConfiguration-${firewallName}`, { launchConfigurationName, @@ -246,7 +279,10 @@ async function createFirewallCluster(props: { } const autoScalingGroup = new elb.CfnAutoScalingGroup(accountStack, `Firewall-AutoScalingGroup-${firewallName}`, { autoScalingGroupName, - launchConfigurationName: launchConfig.ref, + launchTemplate: { + version: '1', + launchTemplateId: launchTemplate.ref, + }, vpcZoneIdentifier: subnetIds, maxInstanceLifetime: maxInstanceAge * 86400, minSize: `${minSize}`, diff --git a/src/deployments/cdk/tsconfig.json b/src/deployments/cdk/tsconfig.json index 756cb6d6c..64a97f17b 100644 --- a/src/deployments/cdk/tsconfig.json +++ b/src/deployments/cdk/tsconfig.json @@ -14,11 +14,5 @@ "resolveJsonModule": true, "typeRoots": ["node_modules/@types"] }, - "include": [ - "src", - "./tools.ts", - "./toolkit.ts", - "./cdk.ts", - "./microstats.d.ts", - ], + "include": ["src", "./tools.ts", "./toolkit.ts", "./cdk.ts", "./microstats.d.ts"] } diff --git a/src/lib/cdk-constructs/src/vpc/asg.ts b/src/lib/cdk-constructs/src/vpc/asg.ts index b44e4c66a..a3d134a52 100644 --- a/src/lib/cdk-constructs/src/vpc/asg.ts +++ b/src/lib/cdk-constructs/src/vpc/asg.ts @@ -15,6 +15,7 @@ import * as cdk from 'aws-cdk-lib'; import { LaunchConfiguration } from '../../src/autoscaling'; import { CfnAutoScalingGroup } from 'aws-cdk-lib/aws-autoscaling'; import { Construct } from 'constructs'; +import { LaunchTemplate } from 'aws-cdk-lib/aws-ec2'; export interface RsysLogAutoScalingGroupProps extends cdk.StackProps { latestRsyslogAmiId: string; @@ -62,10 +63,46 @@ export class RsysLogAutoScalingGroup extends Construct { logGroupName: props.logGroupName, }); + const launchTemplate = new cdk.aws_ec2.CfnLaunchTemplate(this, 'RsyslogLaunchTemplate', { + launchTemplateName: `${props.acceleratorPrefix}RsyslogLaunchConfiguration`, + launchTemplateData: { + networkInterfaces: [ + { + deviceIndex: 0, + associatePublicIpAddress: false, + + groups: [props.securityGroupId], + }, + ], + metadataOptions: { + httpTokens: 'required', + httpEndpoint: 'enabled', + }, + imageId: props.latestRsyslogAmiId, + iamInstanceProfile: { + name: createIamInstanceProfileName(props.instanceRole), + }, + instanceType: props.instanceType, + blockDeviceMappings: [ + { + deviceName: '/dev/xvda', + ebs: { + volumeSize: props.rootVolumeSize, + volumeType: 'gp2', + encrypted: true, + }, + }, + ], + }, + }); + const autoScalingGroupSize = props.desiredInstanceHosts; new CfnAutoScalingGroup(this, 'RsyslogAutoScalingGroup', { autoScalingGroupName: `${props.acceleratorPrefix}RsyslogAutoScalingGroup`, - launchConfigurationName: launchConfig.ref, + launchTemplate: { + launchTemplateId: launchTemplate.ref, + version: '1', + }, vpcZoneIdentifier: props.subnetIds, maxInstanceLifetime: props.maxInstanceAge * 86400, minSize: `${props.minInstanceHosts}`, @@ -84,7 +121,7 @@ export class RsysLogAutoScalingGroup extends Construct { ], }); - let launchConfigUserData = `#!/bin/bash\necho "[v8-stable]\nname=Adiscon CentOS-6 - local packages for \\$basearch\nbaseurl=http://rpms.adiscon.com/v8-stable/epel-6/\\$basearch\nenabled=0\ngpgcheck=0\ngpgkey=http://rpms.adiscon.com/RPM-GPG-KEY-Adiscon\nprotect=1" >> /etc/yum.repos.d/rsyslog.repo\nyum update -y\nyum install -y rsyslog --enablerepo=v8-stable --setopt=v8-stable.priority=1\nchkconfig rsyslog on\naws s3 cp s3://${props.centralBucketName}/rsyslog/rsyslog.conf /etc/rsyslog.conf\nservice rsyslog restart\nwget https://s3.${cdk.Aws.REGION}.amazonaws.com/amazoncloudwatch-agent-${cdk.Aws.REGION}/amazon_linux/amd64/latest/amazon-cloudwatch-agent.rpm\nrpm -U ./amazon-cloudwatch-agent.rpm\ninstanceid=$(curl http://169.254.169.254/latest/meta-data/instance-id)\necho "{\\"logs\\": {\\"logs_collected\\": {\\"files\\": {\\"collect_list\\": [{\\"file_path\\": \\"/var/log/messages\\",\\"log_group_name\\": \\"${props.logGroupName}\\",\\"log_stream_name\\": \\"$instanceid\\"}]}}}}" >> /opt/aws/amazon-cloudwatch-agent/bin/config.json\n/opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -s -a fetch-config -m ec2 -c file:/opt/aws/amazon-cloudwatch-agent/bin/config.json`; + let rsyslogUserData = `#!/bin/bash\necho "[v8-stable]\nname=Adiscon CentOS-6 - local packages for \\$basearch\nbaseurl=http://rpms.adiscon.com/v8-stable/epel-6/\\$basearch\nenabled=0\ngpgcheck=0\ngpgkey=http://rpms.adiscon.com/RPM-GPG-KEY-Adiscon\nprotect=1" >> /etc/yum.repos.d/rsyslog.repo\nyum update -y\nyum install -y rsyslog --enablerepo=v8-stable --setopt=v8-stable.priority=1\nchkconfig rsyslog on\naws s3 cp s3://${props.centralBucketName}/rsyslog/rsyslog.conf /etc/rsyslog.conf\nservice rsyslog restart\nwget https://s3.${cdk.Aws.REGION}.amazonaws.com/amazoncloudwatch-agent-${cdk.Aws.REGION}/amazon_linux/amd64/latest/amazon-cloudwatch-agent.rpm\nrpm -U ./amazon-cloudwatch-agent.rpm\ninstanceid=$(curl http://169.254.169.254/latest/meta-data/instance-id)\necho "{\\"logs\\": {\\"logs_collected\\": {\\"files\\": {\\"collect_list\\": [{\\"file_path\\": \\"/var/log/messages\\",\\"log_group_name\\": \\"${props.logGroupName}\\",\\"log_stream_name\\": \\"$instanceid\\"}]}}}}" >> /opt/aws/amazon-cloudwatch-agent/bin/config.json\n/opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -s -a fetch-config -m ec2 -c file:/opt/aws/amazon-cloudwatch-agent/bin/config.json`; if (props.userData) { /* eslint-disable no-template-curly-in-string */ @@ -94,13 +131,14 @@ export class RsysLogAutoScalingGroup extends Construct { ['\\${SEA:CUSTOM::CentralBucket}', props.centralBucketName], ]); - launchConfigUserData = props.userData; + rsyslogUserData = props.userData; for (const replaceToken of replaceTokens.entries()) { - launchConfigUserData = launchConfigUserData.replace(new RegExp(replaceToken[0], 'g'), replaceToken[1]); + rsyslogUserData = rsyslogUserData.replace(new RegExp(replaceToken[0], 'g'), replaceToken[1]); } } + launchTemplate.addPropertyOverride('LaunchTemplateData.UserData', cdk.Fn.base64(rsyslogUserData)); - launchConfig.userData = cdk.Fn.base64(launchConfigUserData); + launchConfig.userData = cdk.Fn.base64(rsyslogUserData); } }