From d67aa99ef627fd028da6059ad5610866e6332877 Mon Sep 17 00:00:00 2001 From: Guillermo Ramos Date: Tue, 7 Nov 2023 12:52:40 +0100 Subject: ssf: almost full rewrite with pass support --- install.sh | 2 +- passh | 34 +++++++++++++++++++++++++ ssf | 86 +++++++++++++++++++++++++++++++++----------------------------- 3 files changed, 81 insertions(+), 41 deletions(-) create mode 100755 passh diff --git a/install.sh b/install.sh index 824daec..8343cde 100755 --- a/install.sh +++ b/install.sh @@ -1,4 +1,4 @@ #!/bin/sh mkdir -p ~/.local/bin -stow -v "$(dirname "$0")" -t ~/.local/bin --ignore='.*.sh$' +stow -v "$(dirname "$0")" -t ~/.local/bin --ignore='.*\.sh$' diff --git a/passh b/passh new file mode 100755 index 0000000..765c0c9 --- /dev/null +++ b/passh @@ -0,0 +1,34 @@ +#!/bin/sh + +PASS_PREFIX="tc/ssh/" + +host="$1" + +# Open an "anonymous" fifo in fd 3 +pipe="$(mktemp -u)" # get random path +mkfifo "$pipe" # create a fifo there +exec 3<> "$pipe" # open fifo rw at fd 3 +rm "$pipe" # remove file + +echo -n "Trying to get password from 'pass'... " +if pass show "${PASS_PREFIX}$host" 2> /dev/null >&3; then + echo "FOUND ('${PASS_PREFIX}$host'). Connecting..." + exec sshpass -d3 ssh -o StrictHostKeyChecking=accept-new $@ +else + echo "not found. Trying pubkeys..." + ssh -o NumberOfPasswordPrompts=0 -o StrictHostKeyChecking=accept-new $@ + ok=$? + if [ "$ok" -ne 0 ]; then + read -rp "Connection via pubkey failed, password? " passphrase + echo "$passphrase" >&3 + echo "Connecting with passphrase..." + sshpass -d3 ssh -o StrictHostKeyChecking=accept-new $@ + ok=$? + if [ "$ok" -eq 0 ]; then + echo "It worked! Storing password..." + echo "$passphrase" | pass insert -e "${PASS_PREFIX}$host" + else + echo "Unable to connect with password either; giving up." + fi + fi +fi diff --git a/ssf b/ssf index 2366b29..8915c9a 100755 --- a/ssf +++ b/ssf @@ -3,9 +3,11 @@ # Author: Guillermo Ramos # SPDX-License-Identifier: BSD-3-Clause # -# Minimalistic SSH connector which uses FZF, the SSH config and the known_hosts file -# to show a reasonable list of machines to connect to. Then it starts or attaches to -# a remote tmux session named as the current user. +# Minimalistic SSH connector which uses FZF and the SSH config to show a +# reasonable list of machines to connect to. Then it starts or attaches to +# a remote tmux session named as the current user. It also supports passwords +# stored in `pass`, using the wrappers "passh" (provided in this repo" and +# "sshpass". # ################################################################################ @@ -13,68 +15,72 @@ use strict; use warnings; my @hosts; -my %aliases; -my $remote_user = $ENV{SSF_REMOTE_USER} || $ENV{USER}; +my $remote_session_name = $ENV{SSF_REMOTE_USER} || $ENV{USER}; -open(my $ssh_config, '<', "$ENV{HOME}/.ssh/config") or die "nope"; -while (my $line = <$ssh_config>) { - if ($line =~ /^Host ([^#\n]+)(?: # (.*))?$/) { - push @hosts, split(/ /, $1); - if (defined $2) { - $aliases{$_} = $1 foreach split / /, $2; +foreach my $cfgfile (glob "$ENV{HOME}/.ssh/*config") { + open(my $ssh_config, '<', $cfgfile) or die "nope"; + my @line_hosts; + while (my $line = <$ssh_config>) { + my ($key, $value); + if ($line =~ /^\s*([A-Z][a-zA-Z]+)\s+([^#\n]+)/) { + $key = $1; + $value = $2; + } + next unless defined $key; + if ($key eq 'Host') { + @line_hosts = map { { _pat => $_ } } (split /\s/, $value); + push @hosts, @line_hosts; + } elsif ($key eq 'Match') { + @line_hosts = (); + } else { + $_->{$key} = $value foreach (@line_hosts); } } } -my $egrep_regex = join "|", grep { $_ =~ /\*/ } @hosts; -$egrep_regex =~ s/\./\\./g; -$egrep_regex =~ s/\*/.*/g; +my @hosts_no_wild = grep { not $_ =~ /\*/ } (map { $_->{_pat} } @hosts); +my $newlined_hosts = join "\n", sort @hosts_no_wild; -my $known_hosts = ` - grep -E '$egrep_regex' ~/.ssh/known_hosts | - cut -f1 -d" " | - sort -u -`; -my $hosts_no_wild = join "\n", grep { not $_ =~ /\*/ } @hosts; -my $aliases = join "\n", keys %aliases; -my $prompt = @ARGV ? "@ARGV " : ""; +my $fzf_cmd = 'fzf'; +if (exists $ENV{TMUX}) { + if (`which fzf-tmux`) { + $fzf_cmd = 'fzf-tmux'; + } +} + +my $prompt = @ARGV ? "$ARGV[0] " : ""; my $selection = ` - echo -n "${known_hosts}$aliases\n$hosts_no_wild" | - fzf -1 --print-query -q "$prompt" + echo -n "$newlined_hosts" | + $fzf_cmd -1 --print-query -q "$prompt" `; my $retval = $? >> 8; my @lines = split "\n", $selection; my $hostname; if ($retval == 0) { + # 0: normal exit; a host has been selected $hostname = $lines[1]; } elsif ($retval == 1) { + # 1: no match; pick user input $hostname = $lines[0]; } else { + # either 2 (error) or 130 (interrupted wth C-c or ESC) exit $retval; } $hostname =~ s/^\s+|\s+$//g; # Trim whitespace -my $host = $aliases{$hostname} || $hostname; # Resolve alias # Save the final command to the shell history...? -#`print -s "ssh $host"`; +#`print -s "ssh $hostname"`; + +`which sshpass && which passh`; +my @cmd = ($? eq 0 ? 'passh' : 'ssh', $hostname, '-t', "sh -c \"tmux new -As $remote_session_name\""); -# Special behavior for tmux if (exists $ENV{TMUX}) { - my $window_id = `tmux list-windows -F '#{window_index}' -f '#{==:#{window_name},$hostname}'`; - $window_id =~ s/^\s+|\s+$//g; # Trim whitespace - if ($window_id ne '') { - # The window exists; select it instead - `tmux select-window -t $window_id`; - } else { - # The window does not exist; make the connection in the current one - # and it to the machine we're connecting to - `tmux rename-window $hostname`; - system 'ssh', '-t', $host, "sh -c \"tmux new -As $remote_user\""; - `tmux rename-window '!$hostname'`; - } + `tmux rename-window $hostname`; + system 'passh', $hostname, '-t', "sh -c \"tmux new -As $remote_session_name\""; + `tmux rename-window '[$hostname]'`; } else { - exec 'ssh', '-t', $host, "sh -c \"tmux new -As $remote_user\""; + exec 'passh', $hostname, '-t', "sh -c \"tmux new -As $remote_session_name\""; } -- cgit v1.2.3