Security group rule not added to security group

81 Views Asked by At

I'm making a program to launch an ec2 instance, establish an ssh connection, run a simple command and print the result.

With the default security the SSH connection hangs and fails to connect. So I have tried to specify the security to match instances I start manually that I can SSH into.

But when calling authorize_security_group_ingress (rust, cli) the rule is not added to the specified security group.

Output

Parsing command line arguments
Loading aws config
Creating SSH key pair
private key: Some("-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEAiL0crwsT1i+NSSlWtZivjxNN6neNrnklOZYqtwfFeajGH5Ey\nUl6fxCSkqkkCrzWpd4i6/vKyZi+fLFIiCmJjk1DZRo1pCc/BkuOUh+o127EAFjbe\nTm6hIRmr1tKZr2zF0oCh5M8msNRYZrM8SsU2oBOV0xk1axJX/Xkjm7npqGLpn2+h\n9bzrujMNFPmJbD9B3nf5KXd1ZQjf20Et0yhC0yrUWfhNYifietcoVC9rA5VIeQxb\nAUY/xNtYgz13gmBwFYbt/3yNo1KZIaT5bp1aTyt5+lipSDqpj5rmXSbLYB23mocE\ndYjZX3zvM/tGFc0DAacGks/2aHo9l16G2aXNtQIDAQABAoIBAFR3ndRzn1lcHobn\nRmz/WNOlNSh1mcwBggFExjYyUsaIf3rYkqFXWpIroJygZUwedgUlLX54JcQB/u29\n/tEzXheOhemTSSOKdyVp+ELNZ1/Cwy71zfXdWSO9W+1kQxOeucUDOP7DCD+LrOAk\nMEZv9QKFlrPEir8NodUuk9SKz8/4As2GzGXKbrqCHp9Q2ZvBThDEhh7v5Pz6itr9\nlfPMnWc5mjKg8A2cN1bcKwKSKKqUaxBbQrat0NSKyYfa84fue8Shs5Ro/ysdoo7b\nJlV42A6F2ZYDllpva0WLq0/kntgN/AOKL7jaNlsEP8TOpKfImZTB2lb8pbNF5lIG\nIgvHcwECgYEAwoCaObAtsAnSsMCGo8q1rQPAUaUQ/CVqRChNy1LsbCWX8ZH7g+aC\np6KWQf+KWrHFSKpKUCTk5/nAawvPgSxMdGaKGswXBi/LlUProSrPwSJjCi82xWif\nGN+AP2At8ZI16b3588ilUm8rzKNOfJZp1okYpFo1A022n/Ssj8NfZrECgYEAs/kC\nUTMtntxOfvk3hrlr+d2CNx1DnnnF93Ssmzn56jszauLuU8YvWqUjN9XwpZmKDRUC\nhcbz8yJFQa27iuuAfv1wq4BP1F6UNTydt128D1osbEdK/Y8v4MmoXmwwocYij8FY\nnDPrwK1SdT6OOQdPId06yvu6qfR4vFFL4N3pIEUCgYBm820utcspD7n+ppldnxFU\nt9SXIpj/7cn2s6KhyY2snKV1T0DjCyMDGjMQUfNomAoFsWVOUIj9JJwtzP2TsN/z\nCMd28aoKM0g+BMp271MyNkJYBK+oA/2aS8r1QLJw2GRDCbSAziZ7oK59Tb9ggLka\nvkxVyg2fZwYQWpDwM4iOsQKBgQCBu3wqIGRAYbrL2MZn/X2STlSxegzmTg2ghaBu\n/OnkKOy1ngQCq5gzFVs/wp6IIRfcukppOLNdjlSyNZQ9XenwoKz5U7M3+T2I6rse\nFRmdT3k6TGIISZFPzs3p0r9zvinnyo4fe2X0LHyGO6O2BEjMtnbNH4y9OpdV2JyD\n6jNEqQKBgQCepjTrgL+SAdlo2Y9Eonn0b2Mi6IYLZzqQZ6liL5Jx1o2hf4FFduak\ny+cecLenqZlnjaSlD0uHfUC0ogr0cpjx7RNM2vaVCQKHAGPT+CJvrPYPsPux7FQx\nRBSz4qbz54mtByAhBTiHPHCWDsUL4BWm79Yu0k+WycNyzTdpcTTUmA==\n-----END RSA PRIVATE KEY-----")
Creating security groups
Setting ingress security group rule
[src/main.rs:79] authorize_security_group_ingress_response = AuthorizeSecurityGroupIngressOutput {
    return: Some(
        true,
    ),
    security_group_rules: Some(
        [],
    ),
    _request_id: Some(
        "8a1a9d22-c6db-4957-8ac9-7ef8b8b7e0ee",
    ),
}
Launching instances
Polling for instance to enter the `running` state
.....
Getting running instance description
Connecting SSH
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Os { code: 110, kind: TimedOut, message: "Connection timed out" }', src/main.rs:159:60
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

I've deleted the keypair for the key shown here.

Looking at:

    security_group_rules: Some(
        [],
    ),

it shows the security group rule was not added to the security group, this is also reflected in the ec2 console where the security group is created and connected to the instance but does not have this inbound security rule. As documented here it notes security_group_rules is:

Information about the inbound (ingress) security group rules that were added.

What am I missing here?

Source

Run with AWS_ACCESS_KEY_ID=<access key id> AWS_SECRET_ACCESS_KEY=<secret access key> AWS_DEFAULT_REGION=eu-west-2 cargo run

use aws_sdk_ec2 as ec2;
use clap::Parser;
use ec2::types::InstanceType;
use std::io::Read;
use std::io::Write;
use std::str::FromStr;
use std::thread::sleep;
use std::time::Duration;

/// t2.micro
const DEFAULT_INSTANCE: InstanceType = InstanceType::T2Nano;

/// Ubuntu 22.04 LTS
const DEFAULT_AMI: &str = "ami-0eb260c4d5475b901";

const DEFAULT_KEY_NAME: &str = "test-aws-key-pair";

// TODO This should only be default for optional command line argument.
/// The default port used by ec2 for ssh.
const EC2_SSH_PORT: u16 = 22;

// TODO This should only be default for optional command line argument.
const INSTANCE_POLL_STATE_SLEEP: Duration = Duration::from_secs(3);

// TODO This should only be default for optional command line argument.
const SECURITY_GROUP_NAME: &str = "test-aws-security-group";

// TODO This should only be default for optional command line argument.
const SECURITY_GROUP_DESCRIPTION: &str = "test-aws-security-group-description";

#[derive(Parser, Debug)]
struct Args {
    /// The Amazon Machine Image to use for the EC2 instance.
    #[arg(long)]
    ami: Option<String>,
    /// The EC2 instance type to launch.
    #[arg(long)]
    instance: Option<InstanceType>,
    /// Name of the SSH key pair used.
    #[arg(long)]
    key_name: Option<String>,
}

// Run with
// ```
// AWS_ACCESS_KEY_ID=<access key id> AWS_SECRET_ACCESS_KEY=<secret access key> AWS_DEFAULT_REGION=eu-west-2 cargo run
// ```
#[tokio::main]
async fn main() -> Result<(), ec2::Error> {
    println!("Parsing command line arguments");
    let args = Args::parse();

    println!("Loading aws config");
    let config = aws_config::load_from_env().await;
    let client = ec2::Client::new(&config);

    println!("Creating SSH key pair");
    let key_name = args.key_name.as_deref().unwrap_or(DEFAULT_KEY_NAME);
    let builder = client.create_key_pair();
    let create_key_pair_response = builder.key_name(key_name).send().await?;
    println!("private key: {:?}", create_key_pair_response.key_material);

    println!("Creating security groups");
    // The default settings prevent SSH working.
    let builder = client
        .create_security_group()
        .set_group_name(Some(String::from(SECURITY_GROUP_NAME)))
        .set_description(Some(String::from(SECURITY_GROUP_DESCRIPTION)));
    let create_security_group_response = builder.send().await?;
    let security_group_id = create_security_group_response.group_id.unwrap();

    // Set inbound rule (the default outbound rule is fine).
    println!("Setting ingress security group rule");
    let builder = client
        .authorize_security_group_ingress()
        .set_group_id(Some(security_group_id.clone()))
        .set_ip_protocol(Some(String::from("tcp")))
        .set_from_port(Some(22))
        .set_to_port(Some(22));
    let authorize_security_group_ingress_response = builder.send().await?;

    dbg!(authorize_security_group_ingress_response);

    // let builder = client.modify_security_group_rules().set_group_id(Some(security_group_id.clone())).set_security_group_rules(Some(vec![ec2::types::SecurityGroupRuleUpdate::builder().set_security_group_rule_id(input).buid()]));

    println!("Launching instances");
    let instance = args.instance.unwrap_or(DEFAULT_INSTANCE);
    let ami = args.ami.unwrap_or_else(|| String::from(DEFAULT_AMI));
    let builder = client
        .run_instances()
        .set_instance_type(Some(instance))
        .set_image_id(Some(ami))
        .set_max_count(Some(1))
        .set_min_count(Some(1))
        .set_key_name(Some(String::from(key_name)))
        .set_security_group_ids(Some(vec![security_group_id]));
    let run_instances_response = builder.send().await?;

    let Some([instance_response]) = &run_instances_response.instances.as_deref() else {
        panic!()
    };

    // The instance is not immediately assigned a public IP address so we need to wait.
    println!("Polling for instance to enter the `running` state");
    let instance_id = instance_response.instance_id.as_ref().unwrap();
    loop {
        sleep(INSTANCE_POLL_STATE_SLEEP);

        print!(".");
        // `print!` doesn't flush stdout so we need to do it manually.
        std::io::stdout().flush().unwrap();

        let builder = client
            .describe_instance_status()
            .set_instance_ids(Some(vec![instance_id.clone()]))
            // By default this doesn't return descriptions for instances outside the `running`
            // state, in our case we want these description, so we set this parameter.
            .set_include_all_instances(Some(true));
        let describe_instance_status_response = builder.send().await?;
        let Some([instance_status]) = &describe_instance_status_response
            .instance_statuses
            .as_deref()
        else {
            panic!("{describe_instance_status_response:?}")
        };
        if let Some(ec2::types::InstanceState {
            name: Some(ec2::types::InstanceStateName::Running),
            ..
        }) = instance_status.instance_state
        {
            break;
        }
    }
    println!();

    println!("Getting running instance description");
    let builder = client
        .describe_instances()
        .set_instance_ids(Some(vec![instance_id.clone()]));
    let describe_instances_response = builder.send().await?;
    let Some(
        [ec2::types::Reservation {
            instances: Some(instance_descriptions),
            ..
        }],
    ) = describe_instances_response.reservations.as_deref()
    else {
        panic!()
    };
    let [ec2::types::Instance {
        public_ip_address: Some(instance_ip),
        ..
    }] = instance_descriptions.as_slice()
    else {
        panic!()
    };

    let ipv4_address = std::net::Ipv4Addr::from_str(instance_ip).unwrap();
    let socket_address =
        std::net::SocketAddr::V4(std::net::SocketAddrV4::new(ipv4_address, EC2_SSH_PORT));
    println!("Connecting SSH");
    let tcp = std::net::TcpStream::connect(socket_address).unwrap();
    let mut ssh = ssh2::Session::new().unwrap();
    ssh.set_tcp_stream(tcp);
    ssh.handshake().unwrap();
    ssh.userauth_pubkey_memory(
        "ubuntu",
        None,
        create_key_pair_response.key_material.as_deref().unwrap(),
        None,
    )
    .unwrap();
    assert!(ssh.authenticated());

    println!("Running test");
    let mut channel = ssh.channel_session().unwrap();
    channel.exec("ls").unwrap();
    let mut s = String::new();
    channel.read_to_string(&mut s).unwrap();
    println!("{}", s);
    channel.wait_close().unwrap();
    println!("{}", channel.exit_status().unwrap());

    println!("Waiting");
    sleep(Duration::from_secs(30));

    println!("Terminate instances");

    let builder = client
        .terminate_instances()
        .set_instance_ids(Some(vec![instance_id.clone()]));
    let terminate_instances_response = builder.send().await?;

    dbg!(terminate_instances_response);

    println!("Deleting key pair");
    let builder = client
        .delete_key_pair()
        .set_key_name(Some(String::from(key_name)));
    let delete_key_pair_response = builder.send().await?;

    dbg!(delete_key_pair_response);

    Ok(())
}

0

There are 0 best solutions below