aws_throwaway/lib.rs
1mod backend;
2mod ec2_instance;
3mod ec2_instance_definition;
4mod ssh;
5
6use std::net::IpAddr;
7
8pub use backend::{Aws, InstanceType, PlacementStrategy};
9pub use ec2_instance::{Ec2Instance, NetworkInterface};
10pub use ec2_instance_definition::{Ec2InstanceDefinition, InstanceOs};
11
12// include a magic number in the keyname to avoid collisions
13// This can never change or we may fail to cleanup resources.
14const USER_TAG_NAME: &str = "aws-throwaway-23c2d22c-d929-43fc-b2a4-c1c72f0b733f:user";
15const APP_TAG_NAME: &str = "aws-throwaway-23c2d22c-d929-43fc-b2a4-c1c72f0b733f:app";
16
17pub struct AwsBuilder {
18 cleanup: CleanupResources,
19 use_public_addresses: bool,
20 ingress_restriction: IngressRestriction,
21 vpc_id: Option<String>,
22 az_name: Option<String>,
23 subnet_id: Option<String>,
24 placement_strategy: PlacementStrategy,
25 security_group_id: Option<String>,
26 expose_ports_to_internet: Vec<u16>,
27}
28
29/// The default configuration will succeed for an AMI user with sufficient access and unmodified default vpcs/subnets
30/// Consider altering the configuration if:
31/// * you want to reduce the amount of access required by the user
32/// * you want to connect directly from within the VPC
33/// * you have already created a specific VPC, subnet or security group that you want aws-throwaway to make use of.
34///
35/// All resources will be created in us-east-1.
36/// This is hardcoded so that aws-throwaway only has to look into one region when cleaning up.
37/// All instances are created in a single spread placement group in a single AZ to ensure consistent latency between instances.
38// TODO: document minimum required access for default configuration.
39impl AwsBuilder {
40 fn new(cleanup: CleanupResources) -> Self {
41 AwsBuilder {
42 cleanup,
43 use_public_addresses: true,
44 ingress_restriction: IngressRestriction::NoRestrictions,
45 vpc_id: None,
46 az_name: None,
47 subnet_id: None,
48 // refer to: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/placement-groups.html
49 // I believe Spread is the best default since it is the easiest for amazon to fulfill and gives the most realistic results in benchmarks.
50 placement_strategy: PlacementStrategy::Spread,
51 security_group_id: None,
52 expose_ports_to_internet: vec![],
53 }
54 }
55
56 /// When set to:
57 /// * true => aws-throwaway will connect to the public ip of the instances that it creates.
58 /// + The subnet must have the property MapPublicIpOnLaunch set to true (the unmodified default subnet meets this requirement)
59 /// + Elastic IPs will be created for instances with multiple network interfaces because AWS does not assign a public IP in that scenario
60 /// * false => aws-throwaway will connect to the private ip of the instances that it creates.
61 /// + aws-throwaway must be running on a machine within the VPC used by aws-throwaaway or a VPN must be used to connect to the VPC or another similar setup.
62 ///
63 /// If the subnet used has MapPublicIpOnLaunch=true then all instances will be publically accessible regardless of this use_public_addresses field.
64 ///
65 /// The default is `true`.
66 pub fn use_public_addresses(mut self, use_public_addresses: bool) -> Self {
67 self.use_public_addresses = use_public_addresses;
68 self
69 }
70
71 pub fn use_ingress_restriction(mut self, ingress_restriction: IngressRestriction) -> Self {
72 self.ingress_restriction = ingress_restriction;
73 self
74 }
75
76 /// * Some(_) => All resources will go into the specified vpc
77 /// * None => All resources will go into the default vpc
78 ///
79 /// The default is `None`
80 pub fn use_vpc_id(mut self, vpc_id: Option<String>) -> Self {
81 self.vpc_id = vpc_id;
82 self
83 }
84
85 /// * Some(_) => All resources will go into the specified AZ
86 /// * None => All resources will go into the default AZ (us-east-1c)
87 ///
88 /// The default is `None`
89 pub fn use_az(mut self, az_name: Option<String>) -> Self {
90 self.az_name = az_name;
91 self
92 }
93
94 /// * Some(_) => All instances will go into the specified subnet
95 /// * None => All instances will go into the default subnet for the specified or default vpc
96 ///
97 /// The default is `None`
98 pub fn use_subnet_id(mut self, subnet_id: Option<String>) -> Self {
99 self.subnet_id = subnet_id;
100 self
101 }
102
103 /// All EC2 instances are created within a single placement group with the specified strategy.
104 ///
105 /// The default is `PlacementStrategy::Spread`
106 pub fn use_placement_strategy(mut self, placement_strategy: PlacementStrategy) -> Self {
107 self.placement_strategy = placement_strategy;
108 self
109 }
110
111 /// * Some(_) => All instances will use the specified security group
112 /// * None => A single security group will be created for all instances to use. It will allow:
113 /// + ssh traffic in from the internet
114 /// + all traffic out to the internet
115 /// + all traffic in+out between instances in the security group, i.e. all ec2 instances created by this [`Aws`] instance
116 ///
117 /// The default is `None`
118 pub fn use_security_group_id(mut self, security_group_id: Option<String>) -> Self {
119 self.security_group_id = security_group_id;
120 self
121 }
122
123 /// Adds the provided ports as allowing traffic in+out to internet in the automatically generated security group.
124 /// By default ingress is allowed from port 22 and this cannot be disabled.
125 pub fn expose_ports_to_internet(mut self, ports: Vec<u16>) -> Self {
126 self.expose_ports_to_internet = ports;
127 self
128 }
129
130 /// Builds the Aws instance.
131 ///
132 /// Will panic if both `expose_ports_to_internet` and `use_security_group_id` are enabled.
133 pub async fn build(self) -> Aws {
134 if !self.expose_ports_to_internet.is_empty() && self.security_group_id.is_some() {
135 panic!("Both `use_security_group_id` and `expose_ports_to_internet` are set. Ensure only one of these options is set.")
136 }
137 Aws::new(self).await
138 }
139}
140
141/// Specify the cleanup process to use.
142pub enum CleanupResources {
143 /// Cleanup resources created by all [`Aws`] instances that use [`CleanupResources::WithAppTag`] of the same tag.
144 /// It is highly reccomended that this tag is hardcoded, generating this tag could easily lead to forgotten resources.
145 WithAppTag(String),
146 /// Cleanup resources created by all [`Aws`] instances regardless of whether it was created via [`CleanupResources::AllResources`] or [`CleanupResources::WithAppTag`]
147 AllResources,
148}
149
150/// Defines how to derive the ingress rules of the generated security group for external access.
151///
152/// Internal network traffic between instances created through aws-throwaway is always allowed,
153/// regardless of the `IngressRestriction` value used.
154///
155/// These rules apply to the always enabled port 22 and any extra ports enabled by `AwsBuilder::expose_ports_to_internet`.
156#[non_exhaustive]
157pub enum IngressRestriction {
158 /// Allow ingress from any machine on the internet.
159 /// Many corporate environments will disallow this.
160 NoRestrictions,
161 /// Allow ingress only from the public IP address of the machine aws-throwaway is running on.
162 /// Possibly slightly slower to startup, the public IP will be fetched from https://api.ipify.org in parallel to other work.
163 LocalPublicAddress,
164 // In the future we might add:
165 //UseSpecificAddress(IpAddr)
166}
167
168impl IngressRestriction {
169 async fn cidr_ip(&self) -> String {
170 match self {
171 IngressRestriction::NoRestrictions => "0.0.0.0/0".to_owned(),
172 IngressRestriction::LocalPublicAddress => {
173 let api = "https://api.ipify.org";
174 let ip = reqwest::get(api).await.unwrap().text().await.unwrap();
175 // roundtrip through IpAddr to ensure that we did in fact receive an IP.
176 let ip: IpAddr = ip.parse().unwrap();
177 format!("{ip}/32")
178 }
179 }
180 }
181}