Skip to main content

Automate SSH Login, TMUX Session Creation, and Window Setup

Modern Time Technology

That was it... I was finally fed up with amount of time I was spending to SSH into each host manually. I was repeating the same procedure probably hundreds of times every day... Typing the ssh command and the host name, waiting for the initial prompt, entering my password, typing the sudo -i command to get root access, and entering my password again. Just as expected from a modern time worker. But wait, there was more to deal with...

Working with a lot of servers, I need to use my favorite terminal emulator to organize and manage the number of sessions. This means that every window has to be properly named and assigned to a specific session. So before logging into each server, I had to switch to the correct TMUX session (or create a new one), create a new TMUX window, name the window properly, and finally move on to the SSH stage.

I was aware of a script that was created by an ex-colleague. The script automated the process by storing the credentials in a key chain; however, it was written in Python and designed to run on a Mac. Not to mention, that long promised Kerberos implementation was just a mirage. I was more likely to see Unicorns running around in the office than a Kerberos solution. What I needed was a native Linux implementation written with BASH!

Before I begin, here are some details about my environment. I run the scripts on my Fedora 20 instance accessing primarily CentOS servers. The SSH hop script is dependent on components such as the KDE Wallet Manager and the expect tool. The TMUX hop script requires the TMUX tool as can be easily guessed from its name.

KDE Wallet Manager Configuration

The first step is to utilize the KDE Wallet Manager to store the SSH username and password in a secure manner. I simply created a new wallet named ssh and protected it with a GPG key. In addition, I created two entries named ssh_username and ssh_password under the Password folder.

SSH Login Automation

This awesome blog post about how to access the KWalletManager via the command line interface was the key for this implementation. The first time the ssh.hop script is called, KDE Wallet Manager responds with a prompt to decrypt the wallet and from then on you can use the script without any prompts (provided that you allow access not just once). Here is the script:

ssh.hop

#!/bin/bash

if [ "$#" -eq 0 ]
then
    echo "Usage: $0 <hostname> [<username>]"
    exit
fi

hostname=$1

namedPipe=/tmp/expect.$$
trap "rm -f $namedPipe; exit;" SIGHUP SIGINT SIGQUIT SIGTERM EXIT

walletClient="ssh.hop"
walletName="ssh"
walletUsernameEntry="ssh_username"
walletPasswordEntry="ssh_password"

walletId=$(qdbus org.kde.kwalletd /modules/kwalletd org.kde.KWallet.open "$walletName" 0 "$walletClient")

if [ -z "$walletId" ]
then
    echo "Unable to get wallet id."
    exit
fi

if [ -z "$2" ]
then

    username=$(qdbus org.kde.kwalletd /modules/kwalletd readPasswordList $walletId Passwords "$walletUsernameEntry" "$walletClient" | awk '{print $2}')
    
    if [ -z "$username" ]
    then
        echo "Unable to get username from wallet."
        exit
    fi

else
    username=$2
fi

password=$(qdbus org.kde.kwalletd /modules/kwalletd readPasswordList $walletId Passwords "$walletPasswordEntry" "$walletClient" | awk '{print $2}')

if [ -z "$password" ]
then
    echo "Unable to get password from wallet."
    exit
fi

export HISTIGNORE="expect*"

mkfifo $namedPipe

echo "
    spawn ssh -o StrictHostKeyChecking=no -t $hostname sudo -i
    expect {
        \"?assword:\" { send \"$password\r\" }
        timeout exit
    }
    expect {
        \"password for $username:\" { send \"$password\r\" }
        timeout exit
    }
    interact" > $namedPipe &
    
expect -f $namedPipe
rm -f $namedPipe

A couple of things to note here... The named pipe is required so that we can background the echo statement and exit. If we don't utilize a named pipe and use a regular echo "expect commands" | expect -f - call, expect fails to work properly since the first process is needed to be kept alive. Because, by default, interact expects the user to be writing stdin and reading stdout of the expect process itself. You can prevent the pipe from closing by running a command like (echo "expect commands"; cat) | expect -f - but that means your process list would be littered with echo statements containing clear text passwords. Long story short, to prevent that from happening, a named pipe was used to keep the process list clean.

TMUX Session and Window Setup Automation

As I mentioned before I also needed a way to manage my TMUX session. So I wrote the tmux.hop script to automate TMUX session and window setup and kick of an SSH login right away utilizing the ssh.hop script included above.

tmux.hop

#!/bin/bash

if [ "$#" -eq 0 ]
then
    echo "Usage: $0 <hostname> [<session>] [<username>]"
    exit
fi

h=$1
s=$2
u=$3

tmux=$TMUX

# if a session name provided
if [ ! -z "$s" ]
then

    tmux has-session -t "$s" &>/dev/null
    if [ "$?" -ne 0 ]
    then
        TMUX=""
        tmux new-session -d -s "$s"
    fi
    
    tmux new-window -t "$s" -n "$h" "ssh.hop $h $u"
    
    if [ -z "$tmux" ]
    then
        tmux attach-session -d -t "$s"
    else
        tmux switch-client -t "$s"
    fi

    tmux select-window -t "$s:$h"
    
# no session name provided
else
    
    if [ -z "$tmux" ]
    then
        echo "No current tmux session."
        exit
    fi
    
    tmuxCurrentSession=$(tmux display-message -p '#S')
    tmux new-window -n "$h" "ssh.hop $h $u"
    tmux select-window -t "$tmuxCurrentSession:$h"

fi

Conclusion

This solution seems to be kinda ugly and not practical; however, I am already saving a lot of cycles in my workday by using these scripts. So in my book, the technology works... :)

Comments

Popular posts from this blog

Securing Symfony2 REST services with FOSOAuthServerBundle

Overview In my previous article, I wrote about setting up a Symfony2 REST service using FOSRestBundle. However, this REST service was behind a firewall protected by a generic form_login provider. Not really ideal if you wish to open your REST API to other applications. So in this article, I will try to explain how to set up FOSOAuthServerBundle to protect your REST API methods using OAuth2. Before we start getting into the gritty details, it is a good idea to have a look at the official OAuth2 documentation . Let's begin... FOSOAuthServerBundle Installation You have to install v1.1.0 of FOSOAuthServerBundle if you are using Symfony 2.0.x. If not, see the docs . First, add the following entries to your deps file: [FOSOAuthServerBundle] git=git://github.com/FriendsOfSymfony/FOSOAuthServerBundle.git target=bundles/FOS/OAuthServerBundle version=origin/1.1.x [oauth2-php] git=git://github.com/FriendsOfSymfony/oauth2-php.git Run the vendors script to install these...

Unexpected token "name" of value "if" ("end of statement block" expected) in "WebProfilerBundle:Collector:logger.html.twig"

Encountered this WebProfilerBundle error message when I ran the bin/vendors script to update my Symfony2 bundles. Make sure your deps file is up to date; you need to pay special attention to your version values. In this case, update your twig version to v1.2.0 as illustrated below: [twig] git=http://github.com/fabpot/Twig.git version=v1.2.0 Run the vendors script to update your bundle and the error message should disappear. You can get the most up to date deps file from the symfony-standard repository located at: https://github.com/symfony/symfony-standard/blob/master/deps

A Parcelable Tutorial for Android

Parcelable Interface Overview In one of my earlier posts, I mentioned writing an article about FOSOAuthBundle integration with an Android client. To keep that article to the point, I need to explain some concepts beforehand. One of the important concepts is the Android Parcelable interface that allows data to be transferred between different processes/threads. Certain network operations with Android such as authentication with OAuth2 and then fetching data from a REST endpoint should be performed in the background in order not to block the UI thread. This requires data to be fetched by a service (I have opted for Intent Services in my implementation) in the background and then passed back to the calling activity/fragment with a result callback. This is where the Parcelable interface comes into play. Basically, the Parcelable interface allows your classes to be flattened inside a message container called a Parcel to facilitate high performance inter process communication. The rece...