In today’s dynamic infrastructure landscape, efficiently managing SSH connections to EC2 instances is crucial for seamless operations. This guide introduces aws-bassh, a tool designed to simplify and automate SSH access, ensuring quick and secure connections while accommodating the evolving needs of modern cloud environments.
SSH in a nutshell
SSH (Secure Shell) is a protocol used to connect to remote machines, providing a secure command-line interface for managing and operating the remote system. It is widely used by system administrators and DevOps professionals in their daily tasks due to its robustness and security.
To establish a secure SSH connection with a remote machine, you need three key elements:
- IP Address: The IP address of the remote machine.
- Username: The username for the account you wish to use to connect.
- Private Key File: A private key file to authenticate and secure the connection.
With these three elements, you can use the following command structure to connect to the remote machine:
ssh -i path/to/private.key user@address
Once connected, you can perform various tasks, such as managing files, processes, and configuring the system, provided the connected user has the necessary permissions.
The required IP address, username, and private key file can typically be obtained from your cloud provider or system administrator if you are using bare-metal servers. In this article, we will focus exclusively on connecting to EC2 instances running on AWS.
SSH to an EC2 Machine
There are two primary methods to connect to an EC2 instance: using the command line and using the web interface.
Connecting via the Command Line
To connect via the command line, you need the following information:
- IP Address: Retrieve this from the AWS Management Console or AWS CLI.
- Username: Also available from the AWS Management Console or AWS CLI.
- Private Key File: This file is generated by you when you create the instance. AWS does not store this file. If you launched the instance, you should have the key file. Otherwise, request it from the individual who created the instance.
With these details, you can connect using the ssh command as follows:
ssh -i path/to/private.key user@address
Connecting via the Web Interface
Given the complexity of managing private keys and the effort required to securely distribute them, AWS introduced EC2 Instance Connect. This feature allows you to connect to a remote instance directly from your web browser with just a few clicks.
While the web interface offers ease of use, it has limitations. It does not provide the advanced features of a dedicated terminal emulator, such as split panes, tabs, and familiar shortcuts. Essentially, it attempts to emulate a terminal within a browser tab, which may not meet the needs of all users.
For those who prefer a more robust terminal experience, using a dedicated terminal emulator like Terminator allows for predefined layouts and the ability to connect to multiple machines simultaneously, offering a more efficient and customizable environment.
Using Bash Aliases
Connecting to EC2 instances via the command line can also be inconvenient, as it requires frequent access to the AWS console or executing complex AWS CLI commands to retrieve the necessary connection details. Ideally, I want to simply type the name of the machine in the command line to start a new SSH session.
This can be achieved with a simple bash function that connects to a specific machine. For example:
function my_lovely_ec2_machine() {
ssh -i /path/to/key.pem user@54.32.13.12
}
Adding this function to your ~/.bashrc file makes it available for all shell sessions, enabling quick and easy connections.
However, there are several challenges with this approach:
- Manual Effort: Creating and maintaining these aliases requires manual work.
- Dynamic IP Addresses: EC2 instances may be stopped and started, causing their IP addresses to change.
- Instance Lifecycle: Instances may be frequently created and terminated, making it cumbersome to maintain an alias for each machine.
Due to these reasons, while bash aliases can simplify connections, they may not be practical for environments with frequently changing instances.
Introducing aws-bassh
To address the challenges of connecting to remote EC2 instances from the command line, I developed a tool called aws-bassh. The name is a playful take on “AWS Bash SSH,” reflecting its purpose of simplifying the use of bash to connect to AWS machines.
How aws-bassh Works
aws-bassh operates in two phases: the generate phase and the connect phase.
-
Generate Phase: During this phase, aws-bassh dynamically scans all instances in EC2 and generates a “connect” function for each one. This function is a bash function that leverages the
connectphase of aws-bassh. It uses the instance ID to retrieve its IP address, SSH user, and key name, then constructs and executes an ssh command with those details. The output of the generate phase is a bash script containing a function for each instance, named after the instance itself. You can add this bash script to your ``.bashrc` file to make these functions available in all shell sessions. -
Connect Phase: This phase uses the instance ID to retrieve the necessary connection details (IP address, SSH user, and key name) and executes the ssh command.
Features
Dynamic Instance Management: Automatically generate bash functions for all EC2 instances. Customization: Supports overriding default values using special tags like SSHUser, allowing the use of a different user than the default for the distribution. Bastion Host Support: Allows the use of a Bastion (Jump Server) if the public IP is unavailable or if specified via command line arguments.
With aws-bassh, connecting to your EC2 instances becomes a seamless process, allowing you to initiate SSH sessions by simply typing the name of the machine. This tool significantly reduces the manual effort and complexity involved in managing SSH connections to dynamic EC2 environments.
Usage
For detailed instructions, please refer to the README page of the GitHub repository where aws-bassh is hosted. Here, I’ll provide a brief overview to give you a sense of how it works.
Generating Connection Functions
First, generate a list of “connect” functions by running the following command:
awsbassh generate --profile <PROFILE_NAME> --keys <keys_directory> --output-file /some/secure/path/prod-machines.sh
This command uses the credentials from the specified AWS profile (<PROFILE_NAME>) to fetch the list of EC2 instances. It gathers all the necessary information to connect to each instance. AWS provides only the name of the private key used for connection; this name is appended to the <keys_directory> folder to get the full path. Ensure you have a local directory with your private keys, all set with the correct permissions (chmod 600 path/to/key.pem).
Adding to .bashrc
Once the script is generated, add it to your .bashrc file by including the following line:
source /some/secure/path/prod-machines.sh
Connecting to Instances
After updating your .bashrc, open a new shell session. Type ec2_ followed by tab+tab for autocomplete. You will see a list of EC2 instances. Select one and press enter to connect to it.
The ec2_ prefix is the default prefix aws-bassh assigns to every machine. You can change this by specifying a different prefix in the generate command.
This approach simplifies the process of connecting to EC2 instances, allowing you to quickly and easily initiate SSH sessions by typing the name of the instance in the command line.
Periodic Generation
Due to the dynamic nature of modern infrastructure, machines may be terminated and created frequently. Therefore, it is important to be able to “refresh” the list of machines as needed. To facilitate this, I usually include a function like the following in my .bashrc file:
function refresh_prod_env() {
awsbassh generate \
--profile "production" \
--prefix "prod_" \
--output-file "/some/path/production-machines.sh" \
--keys "/path/to/keys-directory"
if ! grep "production-machines.sh" < "all-machines.sh" >/dev/null; then
echo "source /some/path/production-machines.sh" >> "all-machines.sh"
fi
}
Whenever machines are terminated or created and I need to SSH into them, I run refresh_prod_env just before starting the connection. This command ensures that the list of machines is up-to-date, allowing seamless and accurate connections to the current infrastructure.
Future Development
Despite its name aws-bassh has the potential to support various cloud providers and offer many more options and configurations. At present, it includes all the features I need, as it is primarily used by me and a few friends. However, I am enthusiastic about extending its capabilities. If you have specific requirements or suggestions, feel free to open an issue on the GitHub repository. I will consider implementing it, provided it is not overly complex and presents an interesting challenge.
Final Words on SSHing to Machines
In today’s world, it is often considered best practice to avoid connecting directly to individual machines, adhering to the concept of treating infrastructure like cattle, not pets. While this approach suits many, I prefer the ability to SSH into machines for closer inspection. Direct access allows me to identify issues that standard monitoring and logging might not reveal.
Despite the trend towards automated scaling and ephemeral instances, I find value in manually checking the machines I manage. This hands-on approach has enabled me to spot anomalies, understand program behavior in real-time, and resolve issues more quickly and cost-effectively. While this might seem old-fashioned, it has proven invaluable in maintaining the performance and stability of my infrastructure.