seamless-glance 1.0.0

Fast, read-only AWS TUI for cloud infrastructure visibility and triage
use crate::{app::App, models::security_group::SecurityGroupInfo};

const SENSITIVE_PORTS: [i32; 4] = [22, 3389, 5432, 3306];

fn permission_open_to_world(perm: &aws_sdk_ec2::types::IpPermission) -> bool {
    let ipv4_world = perm
        .ip_ranges()
        .iter()
        .any(|r| r.cidr_ip().map(|cidr| cidr == "0.0.0.0/0").unwrap_or(false));

    let ipv6_world = perm
        .ipv6_ranges()
        .iter()
        .any(|r| r.cidr_ipv6().map(|cidr| cidr == "::/0").unwrap_or(false));

    ipv4_world || ipv6_world
}

fn permission_includes_port(perm: &aws_sdk_ec2::types::IpPermission, port: i32) -> bool {
    if perm.ip_protocol().unwrap_or("") == "-1" {
        return true;
    }

    matches!(
        (perm.from_port(), perm.to_port()),
        (Some(from), Some(to)) if from <= port && port <= to
    )
}

pub async fn fetch_security_groups(app: &App) -> Vec<SecurityGroupInfo> {
    let resp = match app.aws.ec2.describe_security_groups().send().await {
        Ok(r) => r,
        Err(_) => return vec![],
    };

    resp.security_groups()
        .iter()
        .map(|sg| {
            let inbound = sg.ip_permissions().len();
            let outbound = sg.ip_permissions_egress().len();

            let open_to_world = sg.ip_permissions().iter().any(permission_open_to_world);

            let mut sensitive_public_ports = SENSITIVE_PORTS
                .iter()
                .copied()
                .filter(|port| {
                    sg.ip_permissions().iter().any(|perm| {
                        permission_open_to_world(perm) && permission_includes_port(perm, *port)
                    })
                })
                .collect::<Vec<_>>();

            sensitive_public_ports.sort_unstable();
            sensitive_public_ports.dedup();

            SecurityGroupInfo {
                id: sg.group_id().unwrap_or_default().to_string(),
                name: sg.group_name().unwrap_or("unknown").to_string(),
                vpc_id: sg.vpc_id().unwrap_or("unknown").to_string(),
                inbound_rules: inbound,
                outbound_rules: outbound,
                open_to_world,
                sensitive_public_ports,
            }
        })
        .collect()
}