Skip to content

Commit

Permalink
Allow user to restrict ingress rules
Browse files Browse the repository at this point in the history
  • Loading branch information
rukai committed Sep 29, 2024
1 parent cb3b5f6 commit ac17c5c
Show file tree
Hide file tree
Showing 9 changed files with 654 additions and 64 deletions.
596 changes: 558 additions & 38 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions aws-throwaway/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ async-trait = "0.1.30"
serde = { version = "1.0.195", features = ["derive"] }
serde_json = "1.0.111"
futures = "0.3.30"
reqwest = "0.12.0"

[dev-dependencies]
tracing-subscriber = { version = "0.3.1", features = ["env-filter", "json"] }
Expand Down
5 changes: 4 additions & 1 deletion aws-throwaway/examples/aws-throwaway-test-large-file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ async fn main() {
.with_writer(non_blocking)
.init();

let aws = Aws::builder(CleanupResources::AllResources).build().await;
let aws = Aws::builder(CleanupResources::AllResources)
.use_ingress_restriction(aws_throwaway::IngressRestriction::LocalPublicAddress)
.build()
.await;
let instance = aws
.create_ec2_instance(Ec2InstanceDefinition::new(InstanceType::T2Micro))
.await;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ async fn main() {
.init();

println!("Creating instances");
let aws = Aws::builder(CleanupResources::AllResources).build().await;
let aws = Aws::builder(CleanupResources::AllResources)
.use_ingress_restriction(aws_throwaway::IngressRestriction::LocalPublicAddress)
.build()
.await;
let (instance1, instance2) = tokio::join!(
aws.create_ec2_instance(Ec2InstanceDefinition::new(InstanceType::T2Small)),
aws.create_ec2_instance(
Expand Down
5 changes: 4 additions & 1 deletion aws-throwaway/examples/aws-throwaway-test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ async fn main() {
.with_writer(non_blocking)
.init();

let aws = Aws::builder(CleanupResources::AllResources).build().await;
let aws = Aws::builder(CleanupResources::AllResources)
.use_ingress_restriction(aws_throwaway::IngressRestriction::LocalPublicAddress)
.build()
.await;
let instance = aws
.create_ec2_instance(Ec2InstanceDefinition::new(InstanceType::T2Micro))
.await;
Expand Down
1 change: 1 addition & 0 deletions aws-throwaway/examples/create-instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ async fn main() {
println!("Creating instance of type {instance_type}");

let aws = Aws::builder(CleanupResources::WithAppTag(AWS_THROWAWAY_TAG.to_owned()))
.use_ingress_restriction(aws_throwaway::IngressRestriction::LocalPublicAddress)
.build()
.await;
let instance_type = InstanceType::from_str(&instance_type).unwrap();
Expand Down
25 changes: 17 additions & 8 deletions aws-throwaway/src/backend/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ mod placement_strategy;

use crate::{
backend::cli::instance_type::get_arch_of_instance_type, AwsBuilder, CleanupResources,
Ec2Instance, Ec2InstanceDefinition, InstanceOs, NetworkInterface, APP_TAG_NAME, USER_TAG_NAME,
Ec2Instance, Ec2InstanceDefinition, IngressRestriction, InstanceOs, NetworkInterface,
APP_TAG_NAME, USER_TAG_NAME,
};
use anyhow::{anyhow, Result};
use futures::stream::FuturesUnordered;
Expand Down Expand Up @@ -140,7 +141,8 @@ impl Aws {
&security_group_name,
&builder.vpc_id,
builder.security_group_id,
&builder.expose_ports_to_internet
&builder.expose_ports_to_internet,
builder.ingress_restriction
),
Aws::create_placement_group(&tags, &placement_group_name, builder.placement_strategy),
Aws::get_subnet(builder.subnet_id, az_name.clone())
Expand Down Expand Up @@ -200,6 +202,7 @@ impl Aws {
vpc_id: &Option<String>,
security_group_id: Option<String>,
ports: &[u16],
ingress_restriction: IngressRestriction,
) -> String {
match security_group_id {
Some(id) => id,
Expand All @@ -224,24 +227,30 @@ impl Aws {
command.push("--vpc-id");
command.push(vpc_id);
}
let result: SecurityGroup = run_command(&command).await.unwrap();
let (result, cidr_ip) = tokio::join!(
run_command::<SecurityGroup>(&command),
ingress_restriction.cidr_ip()
);
let group_id = result.unwrap().group_id;
tracing::info!("created security group");

let mut futures =
FuturesUnordered::<Pin<Box<dyn Future<Output = ()> + Send>>>::new();
futures.push(Box::pin(Aws::create_ingress_rule_internal(tags, name)));
if !ports.contains(&22) {
// SSH
futures.push(Box::pin(Aws::create_ingress_rule_for_port(tags, name, 22)));
futures.push(Box::pin(Aws::create_ingress_rule_for_port(
tags, name, &cidr_ip, 22,
)));
}
for port in ports {
futures.push(Box::pin(Aws::create_ingress_rule_for_port(
tags, name, *port,
tags, name, &cidr_ip, *port,
)));
}
while futures.next().await.is_some() {}

result.group_id
group_id
}
}
}
Expand All @@ -262,7 +271,7 @@ impl Aws {
tracing::info!("created security group rule - internal");
}

async fn create_ingress_rule_for_port(tags: &Tags, group_name: &str, port: u16) {
async fn create_ingress_rule_for_port(tags: &Tags, group_name: &str, cidr_ip: &str, port: u16) {
let port = port.to_string();
let _result: Ignore = run_command(&[
"ec2",
Expand All @@ -276,7 +285,7 @@ impl Aws {
"--to-port",
&port,
"--cidr-ip",
"0.0.0.0/0",
cidr_ip,
"--tag-specifications",
&tags.create_tags("security-group-rule", &format!("port {port}")),
])
Expand Down
37 changes: 22 additions & 15 deletions aws-throwaway/src/backend/sdk/aws.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use super::tags::Tags;
use crate::ec2_instance::{Ec2Instance, NetworkInterface};
use crate::ec2_instance_definition::{Ec2InstanceDefinition, InstanceOs};
use crate::AwsBuilder;
use crate::CleanupResources;
use crate::{AwsBuilder, IngressRestriction};
use anyhow::anyhow;
use aws_config::meta::region::RegionProviderChain;
use aws_config::retry::ProvideErrorKind;
Expand Down Expand Up @@ -75,7 +75,8 @@ impl Aws {
&security_group_name,
&builder.vpc_id,
builder.security_group_id,
&builder.expose_ports_to_internet
&builder.expose_ports_to_internet,
builder.ingress_restriction,
),
Aws::create_placement_group(
&client,
Expand Down Expand Up @@ -143,20 +144,24 @@ impl Aws {
vpc_id: &Option<String>,
security_group_id: Option<String>,
ports: &[u16],
ingress_restriction: IngressRestriction,
) -> String {
match security_group_id {
Some(id) => id,
None => {
let security_group_id = client
.create_security_group()
.group_name(name)
.set_vpc_id(vpc_id.clone())
.description("aws-throwaway security group")
.tag_specifications(
tags.create_tags(ResourceType::SecurityGroup, "aws-throwaway"),
)
.send()
.await
let (security_group, cidr_ip) = tokio::join!(
client
.create_security_group()
.group_name(name)
.set_vpc_id(vpc_id.clone())
.description("aws-throwaway security group")
.tag_specifications(
tags.create_tags(ResourceType::SecurityGroup, "aws-throwaway"),
)
.send(),
ingress_restriction.cidr_ip()
);
let security_group_id = security_group
.map_err(|e| e.into_service_error())
.unwrap()
.group_id
Expand All @@ -168,15 +173,16 @@ impl Aws {
futures.push(Box::pin(Aws::create_ingress_rule_internal(
client, tags, name,
)));

// SSH
if !ports.contains(&22) {
futures.push(Box::pin(Aws::create_ingress_rule_for_port(
client, tags, name, 22,
client, tags, name, &cidr_ip, 22,
)));
}
for port in ports {
futures.push(Box::pin(Aws::create_ingress_rule_for_port(
client, tags, name, *port,
client, tags, name, &cidr_ip, *port,
)));
}
while futures.next().await.is_some() {}
Expand Down Expand Up @@ -210,6 +216,7 @@ impl Aws {
client: &aws_sdk_ec2::Client,
tags: &Tags,
group_name: &str,
cidr_ip: &str,
port: u16,
) {
let port = port.to_string();
Expand All @@ -219,7 +226,7 @@ impl Aws {
.ip_protocol("tcp")
.from_port(22)
.to_port(22)
.cidr_ip("0.0.0.0/0")
.cidr_ip(cidr_ip)
.tag_specifications(
tags.create_tags(ResourceType::SecurityGroupRule, &format!("port {port}"))
)
Expand Down
43 changes: 43 additions & 0 deletions aws-throwaway/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ mod ec2_instance;
mod ec2_instance_definition;
mod ssh;

use std::net::IpAddr;

pub use backend::{Aws, InstanceType, PlacementStrategy};
pub use ec2_instance::{Ec2Instance, NetworkInterface};
pub use ec2_instance_definition::{Ec2InstanceDefinition, InstanceOs};
Expand All @@ -15,6 +17,7 @@ const APP_TAG_NAME: &str = "aws-throwaway-23c2d22c-d929-43fc-b2a4-c1c72f0b733f:a
pub struct AwsBuilder {
cleanup: CleanupResources,
use_public_addresses: bool,
ingress_restriction: IngressRestriction,
vpc_id: Option<String>,
az_name: Option<String>,
subnet_id: Option<String>,
Expand All @@ -38,6 +41,7 @@ impl AwsBuilder {
AwsBuilder {
cleanup,
use_public_addresses: true,
ingress_restriction: IngressRestriction::NoRestrictions,
vpc_id: None,
az_name: None,
subnet_id: None,
Expand All @@ -64,6 +68,11 @@ impl AwsBuilder {
self
}

pub fn use_ingress_restriction(mut self, ingress_restriction: IngressRestriction) -> Self {
self.ingress_restriction = ingress_restriction;
self
}

/// * Some(_) => All resources will go into the specified vpc
/// * None => All resources will go into the default vpc
///
Expand Down Expand Up @@ -112,6 +121,7 @@ impl AwsBuilder {
}

/// Adds the provided ports as allowing traffic in+out to internet in the automatically generated security group.
/// By default ingress is allowed from port 22 and this cannot be disabled.
pub fn expose_ports_to_internet(mut self, ports: Vec<u16>) -> Self {
self.expose_ports_to_internet = ports;
self
Expand All @@ -136,3 +146,36 @@ pub enum CleanupResources {
/// Cleanup resources created by all [`Aws`] instances regardless of whether it was created via [`CleanupResources::AllResources`] or [`CleanupResources::WithAppTag`]
AllResources,
}

/// Defines how to derive the ingress rules of the generated security group for external access.
///
/// Internal network traffic between instances created through aws-throwaway is always allowed,
/// regardless of the `IngressRestriction` value used.
///
/// These rules apply to the always enabled port 22 and any extra ports enabled by `AwsBuilder::expose_ports_to_internet`.
#[non_exhaustive]
pub enum IngressRestriction {
/// Allow ingress from any machine on the internet.
/// Many corporate environments will disallow this.
NoRestrictions,
/// Allow ingress only from the public IP address of the machine aws-throwaway is running on.
/// Possibly slightly slower to startup, the public IP will be fetched from https://api.ipify.org in parallel to other work.
LocalPublicAddress,
// In the future we might add:
//UseSpecificAddress(IpAddr)
}

impl IngressRestriction {
async fn cidr_ip(&self) -> String {
match self {
IngressRestriction::NoRestrictions => "0.0.0.0/0".to_owned(),
IngressRestriction::LocalPublicAddress => {
let api = "https://api.ipify.org";
let ip = reqwest::get(api).await.unwrap().text().await.unwrap();
// roundtrip through IpAddr to ensure that we did in fact receive an IP.
let ip: IpAddr = ip.parse().unwrap();
format!("{ip}/32")
}
}
}
}

0 comments on commit ac17c5c

Please sign in to comment.