/*******************************************************************************
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License"). You may not use
* this file except in compliance with the License. A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and
* limitations under the License.
* *****************************************************************************
* __ _ _ ___
* ( )( \/\/ )/ __)
* /__\ \ / \__ \
* (_)(_) \/\/ (___/
*
* AWS SDK for .NET
* API Version: 2010-11-15
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using Amazon.EC2.Model;
using Amazon.Util;
using System.Globalization;
namespace Amazon.EC2.Util
{
///
/// This class has utility methods used for setting up a VPC.
///
public static partial class VPCUtilities
{
///
/// Find the current VPC NAT image in the region for the AmazonEC2 client.
///
/// The ec2client used to look up the image.
/// The image
public static Image FindNATImage(IAmazonEC2 ec2Client)
{
if (ec2Client == null)
throw new ArgumentNullException("ec2Client");
List filters = new List()
{
new Filter(){Name = "architecture", Values = new List(){"x86_64"}},
new Filter(){Name = "name", Values = new List(){"ami-vpc-nat-*.x86_64-ebs"}}
};
DescribeImagesResponse imageResponse = ec2Client.DescribeImages(new DescribeImagesRequest() { Filters = filters });
var image = imageResponse.Images.OrderByDescending(x => x.Name).FirstOrDefault();
return image;
}
///
/// This method will look up the current VPC NAT ami in the region and create an instance in the subnet specified.
///
/// The ec2client used to create the NAT instance
/// The properties used to launch the NAT instance.
///
public static Instance LaunchNATInstance(IAmazonEC2 ec2Client, LaunchNATInstanceRequest request)
{
if (ec2Client == null)
throw new ArgumentNullException("ec2Client");
if (request == null)
throw new ArgumentNullException("request");
if (string.IsNullOrEmpty(request.SubnetId))
throw new InvalidOperationException("request.SubnetId is null");
if (string.IsNullOrEmpty(request.InstanceType))
throw new InvalidOperationException("request.InstanceType is null");
List filters = new List()
{
new Filter(){Name = "architecture", Values = new List(){"x86_64"}},
new Filter(){Name = "name", Values = new List(){"ami-vpc-nat-*.x86_64-ebs"}}
};
DescribeImagesResponse imageResponse = ec2Client.DescribeImages(new DescribeImagesRequest() { Filters = filters });
var image = FindNATImage(ec2Client);
if (image == null)
{
throw new AmazonEC2Exception("No NAT image found in this region");
}
RunInstancesRequest runRequest = new RunInstancesRequest()
{
InstanceType = request.InstanceType,
KeyName = request.KeyName,
ImageId = image.ImageId,
MinCount = 1,
MaxCount = 1,
SubnetId = request.SubnetId
};
RunInstancesResponse runResponse = ec2Client.RunInstances(runRequest);
string instanceId = runResponse.Reservation.Instances[0].InstanceId;
// Can't associated elastic IP address until the instance is available
WaitForInstanceToStartUp(ec2Client, instanceId);
ModifyInstanceAttributeRequest modifyRequest = new ModifyInstanceAttributeRequest()
{
InstanceId = instanceId,
Attribute = "sourceDestCheck",
Value = "false"
};
ec2Client.ModifyInstanceAttribute(modifyRequest);
ec2Client.CreateTags(new CreateTagsRequest()
{
Resources = new List() { instanceId },
Tags = new List() { new Tag() { Key = "Name", Value = "NAT" } }
});
var allocationId = ec2Client.AllocateAddress(new AllocateAddressRequest() { Domain = "vpc" }).AllocationId;
ec2Client.AssociateAddress(new AssociateAddressRequest() { InstanceId = instanceId, AllocationId = allocationId });
var instance = ec2Client.DescribeInstances(new DescribeInstancesRequest() { InstanceIds = new List() { instanceId } }).Reservations[0].Instances[0];
return instance;
}
///
/// This method will create a VPC with a subnet that will have an internet gateway attached making instances available to the internet.
///
/// The ec2client used to create the VPC
/// The properties used to create the VPC.
/// The response contains all the VPC objects that were created.
public static LaunchVPCWithPublicSubnetResponse LaunchVPCWithPublicSubnet(IAmazonEC2 ec2Client, LaunchVPCWithPublicSubnetRequest request)
{
LaunchVPCWithPublicSubnetResponse response = new LaunchVPCWithPublicSubnetResponse();
LaunchVPCWithPublicSubnet(ec2Client, request, response);
return response;
}
///
/// This method will create a VPC with a subnet that will have an internet gateway attached making instances available to the internet.
///
/// The ec2client used to create the VPC
/// The properties used to create the VPC.
/// The response contains all the VPC objects that were created.
private static void LaunchVPCWithPublicSubnet(IAmazonEC2 ec2Client, LaunchVPCWithPublicSubnetRequest request, LaunchVPCWithPublicSubnetResponse response)
{
response.VPC = ec2Client.CreateVpc(new CreateVpcRequest()
{
CidrBlock = request.VPCCidrBlock,
InstanceTenancy = request.InstanceTenancy
}).Vpc;
WriteProgress(request.ProgressCallback, "Created vpc {0}", response.VPC.VpcId);
var describeVPCRequest = new DescribeVpcsRequest() { VpcIds = new List() { response.VPC.VpcId } };
WaitTillTrue(((Func)(() => ec2Client.DescribeVpcs(describeVPCRequest).Vpcs.Count == 1)));
if(!string.IsNullOrEmpty(request.VPCName))
{
ec2Client.CreateTags(new CreateTagsRequest()
{
Resources = new List(){ response.VPC.VpcId},
Tags = new List(){new Tag(){Key = "Name", Value = request.VPCName}}
});
}
response.PublicSubnet = ec2Client.CreateSubnet(new CreateSubnetRequest()
{
AvailabilityZone = request.PublicSubnetAvailabilityZone,
CidrBlock = request.PublicSubnetCiderBlock,
VpcId = response.VPC.VpcId
}).Subnet;
WriteProgress(request.ProgressCallback, "Created public subnet {0}", response.PublicSubnet.SubnetId);
WaitTillTrue(((Func)(() => (ec2Client.DescribeSubnets(new DescribeSubnetsRequest() { SubnetIds = new List() { response.PublicSubnet.SubnetId } }).Subnets.Count == 1))));
ec2Client.CreateTags(new CreateTagsRequest()
{
Resources = new List() { response.PublicSubnet.SubnetId },
Tags = new List() { new Tag() { Key = "Name", Value = "Public" } }
});
response.InternetGateway = ec2Client.CreateInternetGateway(new CreateInternetGatewayRequest()
{
}).InternetGateway;
WriteProgress(request.ProgressCallback, "Created internet gateway {0}", response.InternetGateway.InternetGatewayId);
ec2Client.AttachInternetGateway(new AttachInternetGatewayRequest()
{
InternetGatewayId = response.InternetGateway.InternetGatewayId,
VpcId = response.VPC.VpcId
});
WriteProgress(request.ProgressCallback, "Attached internet gateway to vpc");
response.PublicSubnetRouteTable = ec2Client.CreateRouteTable(new CreateRouteTableRequest()
{
VpcId = response.VPC.VpcId
}).RouteTable;
WriteProgress(request.ProgressCallback, "Created route table {0}", response.PublicSubnetRouteTable.RouteTableId);
var describeRouteTableRequest = new DescribeRouteTablesRequest() { RouteTableIds = new List() { response.PublicSubnetRouteTable.RouteTableId } };
WaitTillTrue(((Func)(() => (ec2Client.DescribeRouteTables(describeRouteTableRequest).RouteTables.Count == 1))));
ec2Client.CreateTags(new CreateTagsRequest()
{
Resources = new List() { response.PublicSubnetRouteTable.RouteTableId },
Tags = new List() { new Tag() { Key = "Name", Value = "Public" } }
});
ec2Client.AssociateRouteTable(new AssociateRouteTableRequest()
{
RouteTableId = response.PublicSubnetRouteTable.RouteTableId,
SubnetId = response.PublicSubnet.SubnetId
});
WriteProgress(request.ProgressCallback, "Associated route table to public subnet");
ec2Client.CreateRoute(new CreateRouteRequest()
{
DestinationCidrBlock = "0.0.0.0/0",
GatewayId = response.InternetGateway.InternetGatewayId,
RouteTableId = response.PublicSubnetRouteTable.RouteTableId
});
WriteProgress(request.ProgressCallback, "Added route for internet gateway to route table {0}", response.PublicSubnetRouteTable.RouteTableId);
response.PublicSubnetRouteTable = ec2Client.DescribeRouteTables(describeRouteTableRequest).RouteTables[0];
}
///
/// This method will create a VPC, a public subnet, private subnet and a NAT EC2 instance to allow EC2 instances in the private
/// subnet to establish outbound connections to the internet.
///
/// The ec2client used to create the VPC
/// The properties used to create the VPC.
/// The response contains all the VPC objects that were created.
public static LaunchVPCWithPublicAndPrivateSubnetsResponse LaunchVPCWithPublicAndPrivateSubnets(IAmazonEC2 ec2Client, LaunchVPCWithPublicAndPrivateSubnetsRequest request)
{
LaunchVPCWithPublicAndPrivateSubnetsResponse response = new LaunchVPCWithPublicAndPrivateSubnetsResponse();
LaunchVPCWithPublicSubnet(ec2Client, request, response);
response.PrivateSubnet = ec2Client.CreateSubnet(new CreateSubnetRequest()
{
AvailabilityZone = request.PrivateSubnetAvailabilityZone ?? response.PublicSubnet.AvailabilityZone,
CidrBlock = request.PrivateSubnetCiderBlock,
VpcId = response.VPC.VpcId
}).Subnet;
WriteProgress(request.ProgressCallback, "Created private subnet {0}", response.PublicSubnet.SubnetId);
WaitTillTrue(((Func)(() => (ec2Client.DescribeSubnets(new DescribeSubnetsRequest() { SubnetIds = new List() { response.PrivateSubnet.SubnetId } }).Subnets.Count == 1))));
ec2Client.CreateTags(new CreateTagsRequest()
{
Resources = new List() { response.PrivateSubnet.SubnetId },
Tags = new List() { new Tag() { Key = "Name", Value = "Private" } }
});
WriteProgress(request.ProgressCallback, "Launching NAT instance");
response.NATInstance = LaunchNATInstance(ec2Client, new LaunchNATInstanceRequest()
{
InstanceType = request.InstanceType,
KeyName = request.KeyName,
SubnetId = response.PublicSubnet.SubnetId
});
WriteProgress(request.ProgressCallback, "NAT instance is available");
var defaultRouteTable = GetDefaultRouteTable(ec2Client, response.VPC.VpcId);
if (defaultRouteTable == null)
throw new AmazonEC2Exception("No default route table found for VPC");
ec2Client.CreateRoute(new CreateRouteRequest()
{
RouteTableId = defaultRouteTable.RouteTableId,
DestinationCidrBlock = "0.0.0.0/0",
InstanceId = response.NATInstance.InstanceId
});
WriteProgress(request.ProgressCallback, "Added route to the NAT instance in the default route table");
if (request.ConfigureDefaultVPCGroupForNAT)
{
var defaultSecurityGroup = GetDefaultSecurityGroup(ec2Client, response.VPC.VpcId);
var groupId = ec2Client.CreateSecurityGroup(new CreateSecurityGroupRequest()
{
VpcId = response.VPC.VpcId,
GroupName = "NATGroup",
Description = "Give EC2 Instances access through the NAT"
}).GroupId;
WriteProgress(request.ProgressCallback, "Created security group for NAT configuration");
IpPermission spec = new IpPermission
{
IpProtocol = "-1",
IpRanges = new List{ "0.0.0.0/0"},
UserIdGroupPairs = new List() { new UserIdGroupPair() { GroupId = groupId } }
};
ec2Client.AuthorizeSecurityGroupIngress(new AuthorizeSecurityGroupIngressRequest()
{
IpPermissions = new List() { spec },
GroupId = defaultSecurityGroup.GroupId
});
WriteProgress(request.ProgressCallback, "Added permission to the default security group {0} to allow traffic from security group {1}", defaultSecurityGroup.GroupId, groupId);
response.NATSecurityGroup = ec2Client.DescribeSecurityGroups(new DescribeSecurityGroupsRequest()
{
GroupIds = new List(){ groupId }
}).SecurityGroups[0];
}
return response;
}
private static RouteTable GetDefaultRouteTable(IAmazonEC2 ec2Client, string vpcId)
{
var filters = new List()
{
new Filter() { Name = "vpc-id", Values = new List() { vpcId } },
new Filter() { Name = "association.main", Values = new List() { "true" } }
};
var response = ec2Client.DescribeRouteTables(new DescribeRouteTablesRequest() { Filters = filters });
if (response.RouteTables.Count != 1)
return null;
return response.RouteTables[0];
}
private static SecurityGroup GetDefaultSecurityGroup(IAmazonEC2 ec2Client, string vpcId)
{
var filters = new List()
{
new Filter() { Name = "vpc-id", Values = new List() { vpcId } },
new Filter() { Name = "group-name", Values = new List() { "default" } }
};
var response = ec2Client.DescribeSecurityGroups(new DescribeSecurityGroupsRequest() { Filters = filters });
if (response.SecurityGroups.Count != 1)
return null;
return response.SecurityGroups[0];
}
private static Instance WaitForInstanceToStartUp(IAmazonEC2 ec2Client, string instanceId)
{
var describeRequest = new DescribeInstancesRequest() { InstanceIds = new List() { instanceId } };
for (int tries = 0; tries < 40; tries++)
{
AWSSDKUtils.Sleep(10 * 1000);
var result = ec2Client.DescribeInstances(describeRequest);
if (result.Reservations.Count != 1 && result.Reservations[0].Instances.Count != 1)
return null;
Instance instance = result.Reservations[0].Instances[0];
// Return the updated instance object if we're out of pending
if (!instance.State.Name.Equals("pending"))
{
return instance;
}
}
return null;
}
}
}