commonware_deployer/ec2/
authorize.rs

1//! `authorize` subcommand for `ec2`
2
3use crate::ec2::{
4    aws::*,
5    deployer_directory,
6    utils::{exact_cidr, get_public_ip, DEPLOYER_MAX_PORT, DEPLOYER_MIN_PORT, DEPLOYER_PROTOCOL},
7    Config, Error, CREATED_FILE_NAME, DESTROYED_FILE_NAME, MONITORING_REGION,
8};
9use std::{collections::HashSet, fs::File, path::PathBuf};
10use tracing::info;
11
12/// Adds the deployer's IP (or the one provided) to all security groups.
13pub async fn authorize(config_path: &PathBuf, ip: Option<String>) -> Result<(), Error> {
14    // Load configuration
15    let config: Config = {
16        let config_file = File::open(config_path)?;
17        serde_yaml::from_reader(config_file)?
18    };
19    let tag = &config.tag;
20    info!(tag = tag.as_str(), "loaded configuration");
21
22    // Check deployment status
23    let tag_directory = deployer_directory(tag);
24    if !tag_directory.exists() {
25        return Err(Error::DeploymentDoesNotExist(tag.clone()));
26    }
27    let created_file = tag_directory.join(CREATED_FILE_NAME);
28    if !created_file.exists() {
29        return Err(Error::DeploymentNotComplete(tag.clone()));
30    }
31    let destroyed_file = tag_directory.join(DESTROYED_FILE_NAME);
32    if destroyed_file.exists() {
33        return Err(Error::DeploymentAlreadyDestroyed(tag.clone()));
34    }
35
36    // Determine new IP
37    let new_ip = if let Some(ip_str) = ip {
38        // Validate provided IP as IPv4
39        let ip_addr: std::net::IpAddr = ip_str
40            .parse()
41            .map_err(|_| Error::InvalidIpAddress(ip_str.clone()))?;
42        let std::net::IpAddr::V4(ipv4) = ip_addr else {
43            return Err(Error::InvalidIpAddress(ip_str));
44        };
45        ipv4.to_string()
46    } else {
47        // Fetch public IP
48        get_public_ip().await?
49    };
50    info!(
51        ip = new_ip.as_str(),
52        "adding IP to existing security groups"
53    );
54
55    // Determine all regions involved
56    let mut all_regions = HashSet::new();
57    all_regions.insert(MONITORING_REGION.to_string());
58    for instance in &config.instances {
59        all_regions.insert(instance.region.clone());
60    }
61
62    // Update security groups in each region
63    let mut changes = Vec::new();
64    for region in all_regions {
65        let ec2_client = create_ec2_client(Region::new(region.clone())).await;
66        info!(region = region.as_str(), "created EC2 client");
67
68        // Iterate over all security groups
69        let security_groups = find_security_groups_by_tag(&ec2_client, tag).await?;
70        for sg in security_groups {
71            // Check existing permissions
72            let sg_id = sg.group_id().unwrap();
73            let mut already_allowed = false;
74            for perm in sg.ip_permissions() {
75                // We enforce an exact match to avoid accidentally skipping because
76                // of an ingress rule with different ports or protocols.
77                if perm.ip_protocol() == Some(DEPLOYER_PROTOCOL)
78                    && perm.from_port() == Some(DEPLOYER_MIN_PORT)
79                    && perm.to_port() == Some(DEPLOYER_MAX_PORT)
80                {
81                    for ip_range in perm.ip_ranges() {
82                        if ip_range.cidr_ip() == Some(&exact_cidr(&new_ip)) {
83                            already_allowed = true;
84                            break;
85                        }
86                    }
87                    if already_allowed {
88                        break;
89                    }
90                }
91            }
92
93            // Add ingress rule if not already allowed
94            if already_allowed {
95                info!(sg_id, "IP already allowed");
96                continue;
97            }
98            ec2_client
99                .authorize_security_group_ingress()
100                .group_id(sg_id)
101                .ip_permissions(
102                    IpPermission::builder()
103                        .ip_protocol(DEPLOYER_PROTOCOL)
104                        .from_port(DEPLOYER_MIN_PORT)
105                        .to_port(DEPLOYER_MAX_PORT)
106                        .ip_ranges(IpRange::builder().cidr_ip(exact_cidr(&new_ip)).build())
107                        .build(),
108                )
109                .send()
110                .await
111                .map_err(|err| err.into_service_error())?;
112            info!(sg_id, "added ingress rule for IP");
113            changes.push(sg_id.to_string());
114        }
115    }
116    info!(?changes, "IP added to security groups");
117    Ok(())
118}