void Allow(IList <PolicyStatement> statements, IvrSiteSchema schema, IEnumerable <string> buckets, string suffix, params string [] actions)
 {
     if (null != buckets && 0 < buckets.Count())
     {
         statements.Add(new PolicyStatement().Allow().WithActions(actions).WithResources(schema.S3Resources(suffix, buckets.SelectMany(x => x.Csv()).ToArray())));
     }
 }
        public IvrInlinePolicies(string account, string stackId, IvrSiteSchema schema)
        {
            var statements = new List <PolicyStatement> {
                // Role is needed for allowing tools to use EC2 provided credentials
                // see https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-role.html
                new PolicyStatement().Allow().WithActions("sts:AssumeRole")
                .WithResources($"arn:aws:iam::{account}:role/{stackId}*"),            // allow roles defined in this stack
                new PolicyStatement().Allow().WithActions("ec2:StartInstances", "ec2:StopInstances", "ec2:DescribeInstances")
                .WithResources(),
                new PolicyStatement().Allow().WithActions("s3:GetBucketLocation")
                .WithResources(),
                new PolicyStatement().Allow().WithActions("sqs:DeleteMessage", "sqs:GetQueueAttributes", "sqs:GetQueueUrl", "sqs:ReceiveMessage", "sqs:SendMessage")
                .WithResources(),
                new PolicyStatement().Allow().WithActions("cloudwatch:GetMetricData", "cloudwatch:GetMetricStatistics", "cloudwatch:ListMetrics", "cloudwatch:PutMetricData")
                .WithResources(),
                new PolicyStatement().Allow().WithActions("sns:Publish")
                .WithResources(),
                new PolicyStatement().Allow().WithActions("ses:SendEmail", "ses:SendRawEmail")
                .WithResources(),
                new PolicyStatement().Allow().WithActions("events:PutEvents")
                .WithResources(),
            };

            //
            Allow(statements, schema, schema.S3Buckets.ListBucket, "", "s3:ListBucket");
            Allow(statements, schema, schema.S3Buckets.GetObject, "/*", "s3:GetObject");
            Allow(statements, schema, schema.S3Buckets.PutObject, "/*", "s3:PutObject");
            Allow(statements, schema, schema.S3Buckets.DeleteObject, "/*", "s3:DeleteObject");
            //
            Add("IvrPolicy", new PolicyDocument(new PolicyDocumentProps {
                Statements = statements.ToArray(),
            }));
        }
Example #3
0
        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();
            }
        }