public IvrStack(Construct scope, string stackId, StackProps stackProps, IvrSiteSchema schema, IEnumerable <SecurityGroupRule> securityGroupRules) : base(scope, stackId, stackProps) { IVpc vpc = null; var MaxIpsPerSubnet = IvrVpcProps.MaxIpsPerSubnet; if (!string.IsNullOrWhiteSpace(schema.VpcName)) { vpc = Vpc.FromLookup(this, "$VPC", new VpcLookupOptions { VpcName = schema.VpcName, }); // will error if not found //MaxIpsPerSubnet = ???; } else if (!string.IsNullOrWhiteSpace(schema.VpcId)) { vpc = Vpc.FromLookup(this, "$VPC", new VpcLookupOptions { VpcId = schema.VpcId, }); // will error if not found //MaxIpsPerSubnet = ???; } else if (null != schema.VpcProps) { // use provided props to create brand new VPC vpc = new IvrVpc(this, $"VPC", schema.VpcProps); } if (schema.AddVpcS3Gateway) { var s3gw = new GatewayVpcEndpoint(this, $"S3GW", new GatewayVpcEndpointProps { Vpc = vpc, Service = GatewayVpcEndpointAwsService.S3, Subnets = new SubnetSelection[] { new SubnetSelection { SubnetType = SubnetType.PUBLIC, } }, }); } var role = new Role(this, "IVR", new RoleProps { AssumedBy = new ServicePrincipal("ec2.amazonaws.com"), InlinePolicies = new IvrInlinePolicies(stackProps.Env.Account, stackId, schema), }); // Configure inbound security for RDP (and more?) var securityGroup = new SecurityGroup(this, $"Ingress", new SecurityGroupProps { Vpc = vpc, AllowAllOutbound = schema.AllowAllOutbound, }); securityGroupRules.ForEach(rule => securityGroup.WithSecurityGroupRule(rule)); if (schema.AllowAllIntranet) { securityGroup.WithSecurityGroupRule(new IngressRule(Peer.Ipv4($"{vpc.VpcCidrBlock}"), Port.AllTraffic()).WithDescription($"All intranet traffic")); } // Finally - create our instances! var hosts = new List <HostInstance>(); for (var subnetIndex = 0; ++subnetIndex <= Math.Min(vpc.PublicSubnets.Length, schema.MaxSubnets);) { var hostIndexInSubnet = 0; foreach (var group in schema.HostGroups) { var numberOfHosts = Math.Min(group.HostCount, MaxIpsPerSubnet); if (numberOfHosts != group.HostCount) { Console.WriteLine($"Group({group.Name}) host count changed from {group.HostCount} to {numberOfHosts}"); group.HostCount = numberOfHosts; } var instanceProps = IvrInstanceProps.InstanceProps(vpc, vpc.PublicSubnets[subnetIndex - 1], role, securityGroup, group.InstanceProps); for (var hostCount = 0; ++hostCount <= numberOfHosts; ++hostIndexInSubnet) { var hostName = $"{schema.HostNamePrefix}{subnetIndex}{hostIndexInSubnet:00}"; var hostPrimingProps = new HostPrimingProps { HostName = hostName.AsWindowsComputerName(), // must fit into 15 chars WorkingFolder = $"{stackId}".AsWindowsFolder(), AwsAccount = stackProps.Env.Account, AwsRoleName = role.RoleName, RdpProps = schema.RdpProps, EC2Users = schema.EC2Users, DownloadAndInstall = group.DownloadAndInstall, S3iArgs = $"{group.InstallS3i} --verbose", }; var hostCommands = HostPriming.PrimeForS3i(hostPrimingProps) .WithFirewallAllowInbound($"{vpc.VpcCidrBlock}"); hostCommands.WithRenameAndRestart(hostPrimingProps.HostName); instanceProps.KeyName = schema.KeyPairName; instanceProps.UserData = hostCommands.UserData; hosts.Add(new HostInstance { Group = group, Instance = new Instance_(this, hostName.AsCloudFormationId(), instanceProps), }); } } } // associate pre-allocated EIPs var preAllocatedEIPs = schema.PreAllocatedElasticIPs?.SelectMany(s => s.Csv()).ToList() ?? new List <string> { }; var hostsThatRequireEIP = hosts.Where(h => h.Group.UsePreAllocatedElasticIPs).ToList(); if (preAllocatedEIPs.Count < hostsThatRequireEIP.Count) { throw new ArgumentException($"Pre-Allocated Elastic IPs needed: {hostsThatRequireEIP.Count()}, but only {preAllocatedEIPs.Count()} configured in schema.{nameof(IvrSiteSchema.PreAllocatedElasticIPs)}"); } var elasticIPAssociations = hostsThatRequireEIP.Zip(preAllocatedEIPs, (h, a) => { return(new CfnEIPAssociation(this, $"EIPA{h.Instance.InstancePrivateIp}".AsCloudFormationId(), new CfnEIPAssociationProps { AllocationId = a, InstanceId = h.Instance.InstanceId, })); }).ToList(); // execute LINQ now // We have schema.Domain registered in advance if (!string.IsNullOrWhiteSpace(schema.HostedZoneDomain)) { var theZone = HostedZone.FromLookup(this, $"{stackId}_Zone_", new HostedZoneProviderProps { DomainName = schema.HostedZoneDomain, //Comment = "HostedZone created by Route53 Registrar", }); // assign new Elastic IPs as needed if (!string.IsNullOrWhiteSpace(schema.SubdomainEIPs)) { var newElasticIPs = hosts.Where(h => h.Group.AllocateNewElasticIPs).Select(h => { return(new CfnEIP(this, $"EIP{h.Instance.InstancePrivateIp}".AsCloudFormationId(), new CfnEIPProps { // 'standard' or 'vpc': https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-eip.html#cfn-ec2-eip-domain Domain = "vpc", InstanceId = h.Instance.InstanceId, })); }).ToList(); // collect them now to prevent LINQ side effects if (newElasticIPs.Any()) { // Register public Elastic IPs var arNewPublic = new ARecord(this, $"ARecord_Public_NewAlloc".AsCloudFormationId(), new ARecordProps { Zone = theZone, RecordName = $"{schema.SubdomainEIPs}.{theZone.ZoneName}", Target = RecordTarget.FromValues(newElasticIPs.Select(eip => eip.Ref).ToArray()), Ttl = Duration.Seconds(300), }); } else if (elasticIPAssociations.Any()) { // Register public Elastic IPs /* * var arPrePublic = new ARecord(this, $"ARecord_Public_PreAlloc".AsCloudFormationId(), new ARecordProps * { * Zone = theZone, * RecordName = $"{schema.SubdomainEIPs}.{theZone.ZoneName}", * Target = RecordTarget.FromValues(elasticIPAssociations.Select(eipa => eipa.Ref).ToArray()), // ***** how to do that? * Ttl = Duration.Seconds(300), * }); */ foreach (var a in elasticIPAssociations) { Console.WriteLine($"Pre-Allocated Elastic IP Associations: {a.AllocationId}/{a.InstanceId}, {a.Eip}/{a.PrivateIpAddress}, {a.Ref} - please put it to {schema.SubdomainEIPs}.{theZone.ZoneName} ARecord manually"); } } } if (0 < hosts.Count && !string.IsNullOrWhiteSpace(schema.SubdomainHosts)) { // Register private IPs (never changing, as opposed to public - which change on stop/start) addresses of all hosts var arPrivate = new ARecord(this, $"ARecord_Private_".AsCloudFormationId(), new ARecordProps { Zone = theZone, RecordName = $"{schema.SubdomainHosts}.{theZone.ZoneName}", Target = RecordTarget.FromIpAddresses(hosts.Select(h => h.Instance.InstancePrivateIp).ToArray()), Ttl = Duration.Seconds(300), }); } //throw new Exception(); } }
public static WindowsCommands PrimeForS3i(HostPrimingProps props) { var workingFolder = $"C:\\ProgramData\\{props.WorkingFolder}"; var explorerSettingsPath = $"{workingFolder}\\explorer_settings.reg"; var commandsToRun = new WindowsCommands() // working folder and log file .WithNewFolder(workingFolder, setLocation: true) .WithLogFile($"{workingFolder}\\{props.HostName}.log").Log($"User profile: $env:userprofile") // C:\Users\Administrator .WithExplorerSettingsFile(explorerSettingsPath, hidden: 1, hideFileExt: 0) .Log(); // Create RDP user first if (!string.IsNullOrWhiteSpace(props.RdpProps.UserName)) { commandsToRun .WithNewUser(props.RdpProps.UserName, props.RdpProps.Password, props.RdpProps.UserGroups.ToArray()) .WithCredentials(props.RdpProps.UserName, props.RdpProps.Password, "$creds") .WithStartProcess("regedit", $"/s {explorerSettingsPath}", "$creds") .Log(); } // AWS-enable certain users commandsToRun .WithEc2Credentials("$Env:USERNAME", props.AwsAccount, props.AwsRoleName) // current user (Administrator) .WithEc2Credentials(null, props.AwsAccount, props.AwsRoleName) // system, as s3i service account .Log(); // ...and enable more, if needed props.EC2Users.ToList().ForEach(user => { commandsToRun.WithEc2Credentials(user, props.AwsAccount, props.AwsRoleName) .Log(); }); // Download and install whatever is needed if (null != props.DownloadAndInstall && 0 < props.DownloadAndInstall.Count()) { commandsToRun .WithDownloadAndInstall(props.DownloadAndInstall.ToArray()) .Log(); } // If requested, install and run s3i to install the rest indirectly from specific remote configuration if (!string.IsNullOrWhiteSpace(props.S3iArgs)) { commandsToRun .WithDownloadAndInstall($"{HostPrimingProps.S3iRelease} /quiet") .WithEnvironmentVariable("s3i_args", $" --stage {workingFolder}\\s3i {props.S3iArgs}") //.WithCommands("Restart-Service -Name s3i -Force") // install products frome the line above - not before rename/restart ! .Log(); } // $timeout=8; $timer=[Diagnostics.StopWatch]::StartNew();while(($timer.Elapsed.TotalSeconds -lt $timeout)) { Start-Sleep -Seconds 1; Write-Host $timer.Elapsed.TotalSeconds };$timer.Stop(); // To allow SIP communication between hosts // May need to do better than that, but for now, considering IP whitelisting, should be ok //commandsToRun // .WithDisableFirewall() // .Log(); // final touches and reboot commandsToRun.WithDisableUAC(restartComputer: false); // for some reason, separate restart does not work on instances with EIP attached ??? //// anything else to do - before restarting? //commandsToRun.WithRestart("-Force"); // ...reboot to complete fixing UAC/renaming... return(commandsToRun); }