idmgr/lib/idmgr_mod_ssh.sh
2023-04-04 01:56:26 -04:00

566 lines
13 KiB
Bash

#!/bin/bash
#IDM_MOD_SSH_DEPS="s0 id gpg"
# trap 'idm_ssh_kill' 0
# See: https://github.com/kalbasit/ssh-agents
## SSH functions
##########################################
idm_ssh__help ()
{
echo "Secure Shell"
printf " %-20s: %s\n" "ssh ls" "List unlocked keys"
printf " %-20s: %s\n" "ssh pub" "Show public keys"
printf " %-20s: %s\n" "ssh tree" "Show keypairs tree"
printf " %-20s: %s\n" "ssh new [dir]" "Create new ssh key dest dir"
printf " %-20s: %s\n" "ssh add" "Unlock known keypairs"
printf " %-20s: %s\n" "ssh rm" "Lock known keypairs"
printf " %-20s: %s\n" "ssh del" "Delete keypair"
printf " %-20s: %s\n" "ssh enable" "Enable agent"
printf " %-20s: %s\n" "ssh disable" "Disable agent"
printf " %-20s: %s\n" "ssh kill" "Kill agent"
cat <<EOF
Documentation:
You can create a new ssh key with the assistant:
i ssh new
The you can add this key to your agent, it will ask you your key password:
i ssh add
If you want to kill the agent:
i ssh rm
If you want to delete your key files, simply run:
i ssh rm
EOF
}
idm_ssh ()
{
# Argument maangement
if [ "$#" -eq 1 ]; then
local id=$1
idm_ssh__ls $id
return 0
else
local action=$1
local id=$2
shift 2 || true
local opt=${@-}
fi
# Internal override case
# Fallback to command
idm_ssh__help
return 1
}
## Required functions
##########################################
idm_ssh__ls ()
{
local id=$1
local opt=${2:--l}
lib_id_is_enabled $id || return 0
{ ssh-add $opt || true ; } 2>/dev/null | sed 's/^/ /'
}
idm_ssh__disable ()
{
local id=$1
#lib_id_is_enabled $id
lib_id_has_config $id
# Return portion of code to clean
echo "unset SSH_AUTH_SOCK SSH_AGENT_PID"
}
idm_ssh__enable ()
{
local id=$1
lib_id_has_config $id
local socket="${XDG_RUNTIME_DIR}/ssh-agent/${id}/socket"
# Source environment
# if [ -f "${XDG_RUNTIME_DIR}/ssh-agent/${id}/env" ] ; then
# . "${XDG_RUNTIME_DIR}/ssh-agent/${id}/env"
# else
# unset SSH_AUTH_SOCK SSH_AGENT_PID
# fi
unset SSH_AUTH_SOCK SSH_AGENT_PID
# Check status
export SSH_AUTH_SOCK=$socket
if ! idm_ssh__is_agent_working $socket ; then
if [[ "${IDM_NO_BG:-false}" == true ]] || [[ -n "${DIRENV_IN_ENVRC-}" ]] ; then
lib_log WARN "Start of background process disabled because of: IDM_NO_BG=${IDM_NO_BG:-false}"
lib_log TIPS "Run '${0##*/} $id' to start ssh-agent"
else
idm_ssh__agent_start $id
fi
fi
# Display config to load
# >&2 ls -ahl ${XDG_RUNTIME_DIR}/ssh-agent/${id}/
# cat "${XDG_RUNTIME_DIR}/ssh-agent/${id}/env" || true
echo "export SSH_AUTH_SOCK=$SSH_AUTH_SOCK"
}
# LOGOUT
idm_ssh__kill ()
{
local id=$1
local run_dir="${XDG_RUNTIME_DIR}/ssh-agent/${id}"
lib_id_is_enabled $id
#lib_log NOTICE "Cleaning ssh-agent ..."
[ -z "${SSH_AGENT_PID-}" ] && \
[ -f "$run_dir/env" ] && \
. "$run_dir/env"
# Clean ssh-agent process
if kill -0 ${SSH_AGENT_PID-} &>/dev/null; then
/usr/bin/ssh-agent -k >/dev/null
lib_log NOTICE "Kill ssh-agent ..."
fi
#eval "$(/usr/bin/ssh-agent -k 2>/dev/null)"
# Clean ssh-agent env file
[ ! -f "${XDG_RUNTIME_DIR}/ssh-agent/${id}/env" ] || \
rm "${XDG_RUNTIME_DIR}/ssh-agent/${id}/env"
# Disable agent
idm_ssh__disable $id
}
## Extra functions
##########################################
idm_ssh__tree ()
{
local id=$1
if lib_id_has_config $id &>/dev/null; then
tree -C "$HOME/.ssh/$id"
else
tree -C "$HOME/.ssh/"
fi
}
idm_ssh__pub ()
{
local id=$1
local path="$HOME/.ssh"
if lib_id_has_config $id &>/dev/null; then
path="$HOME/.ssh/$id"
fi
head -n 3 "$path"/*.pub
}
idm_ssh__new ()
{
local id=${1-}
local dest=${2-}
local default=
local key_vers=
local key_user=
local key_host=
local key_sizes=
local key_vers="$(date +'%Y%m%d')"
# Guess defaults
default=$(id -un)
if lib_id_has_config $id &>/dev/null; then
default=${login:-$id}
if [ -z "$dest" ]; then
dest="$HOME/.ssh/$id"
fi
else
dest=${dest:-.}
fi
mkdir -p "$dest"
echo "INFO: Key destination dir: $dest"
# Login
while ! grep -q '\w\+' <<< "$key_user"; do
read -rp "> Username [$default]: " ans
key_user="${ans:-$default}"
done
# Host name
default="${hostname:-$(hostname -f)}"
while ! grep -q '[a-zA-Z0-9.-]\+' <<< "$key_host"; do
read -rp "> Hostname [$default]: " ans
#echo ""
key_host="${ans:-$default}"
done
# Keys sizes
default="ns"
echo "Please choose key types:"
echo "n) ed25519 strongest, fast"
echo "s) rsa4096 most compatible, slow"
echo "o) rsa2048 old compatility"
while ! grep -q '[nso]\+' <<< "$key_sizes"; do
echo -n "> Key types [$default]: "
read -n 3 -r ans
echo ""
key_sizes="${ans:-$default}"
done
# Ask password
echo "Define key passphrase for the key(s)."
echo "Leave it empty for no password (not recommemded)."
echo -n "> Key passphrase [none]: "
read -rs key_pass
echo
key_pass="${key_pass:-}"
ans=""
while [ "$ans" != "$key_pass" ]; do
echo -n "> Confirm passphrase: "
read -rs ans
echo
done
# Create keys
local size=$key_sizes
while [ -n "$size" ]; do
local k=${size:0:1}
echo -e "\n> Generating key ..."
set +e
case $k in
n)
ssh-keygen -f "$dest/${key_user}_ed25519_${key_vers}" \
-t ed25519 -a 100 \
-N "$key_pass" \
-C "${key_user}@${key_host}:ed25519_${key_vers}"
;;
s)
ssh-keygen -f "$dest/${key_user}_rsa4096_${key_vers}" \
-t rsa -b 4096 -o -a 500 \
-N "$key_pass" \
-C "${key_user}@${key_host}:rsa4096_${key_vers}"
;;
o)
ssh-keygen -f "$dest/${key_user}_rsa2048_${key_vers}" \
-t rsa -b 2048 -o -a 100 \
-N "$key_pass" \
-C "${key_user}@${key_host}:rsa2048_${key_vers}"
;;
esac
set -e
size=${size:1}
done
echo
echo "INFO: Key(s) has been created in $dest"
}
## Agent functions
##########################################
idm_ssh__is_agent_working ()
{
local socket=$1
local rc=
set +e
SSH_AUTH_SOCK=$socket ssh-add -l &>/dev/null
rc=$?
set -e
if ! [ "$rc" -lt 2 ]; then
[[ -e "$socket" ]] && rm "$socket"
return 1
fi
return 0
}
background() {
>&2 echo "MY COMMAND: $@"
set +e
exec 0>&- || true
exec 1>&- || true
exec 2>&- || true
exec 3>&- || true
"$@" &
local pid=$!
disown $pid
echo $pid
set -e
}
idm_ssh__agent_start() {
# local socket=$1
local id=$1
local life=5d
local socket_dir="${XDG_RUNTIME_DIR}/ssh-agent/${id}"
local socket="${socket_dir}/socket"
# Ensure directory are present
[ -d "$socket_dir" ] || \
mkdir -p "$socket_dir"
# Start the agent
rm "$socket" 2>/dev/null || true
export SSH_AUTH_SOCK=
export SSH_AGENT_PID=
#nohup ssh-agent -D -a "$socket" -t $life 2>&1 >$socket_dir/env &
# local pid=$(background ssh-agent -a "$socket" -t $life)
ssh-agent -a "$socket" -t $life |& grep 'SSH_' > $socket_dir/env
source "$socket_dir/env"
#echo "SSH_AUTH_SOCK=$socket"
#echo "SSH_AGENT_PID=$pid"
# >&2 echo "PID=$pid"
# echo "SSH_AUTH_SOCK=$socket" > $socket_dir/env
# echo "SSH_AGENT_PID=$pid" >> $socket_dir/env
# # local pid=$!
# # ps aux | grep $pid >&2
# # Wait for service to be started
# . $socket_dir/env > /dev/null
# until [ ! -z "${SSH_AUTH_SOCK:-}" ]; do
# . $socket_dir/env > /dev/null
# >&2 echo "WAiting socket .... "
# sleep 3
# done
# # . $socket_dir/env
# >&2 jobs
# disown -ar
# >&2 jobs
# return
#local run_dir="${XDG_RUNTIME_DIR}/ssh-agent/${id}"
# Check if we can recover from previous instance
# idm_ssh__agent_clean $id "$run_dir/socket" 0 || true
# # Ensure env file is not present
# [ ! -f "${run_dir}/env" ] || \
# rm -f "${run_dir}/env"
# #set -x
# DEVEL # Start the agent
# DEVEL lib_log INFO "Start ssh-agent ..."
# DEVEL $IDM_DIR_ROOT/bin/start_ssh_agent.sh "$run_dir/socket" $life
# DEVEL
# DEVEL # nohup ssh-agent -D -a "$run_dir/socket" -t $life
# DEVEL # export SSH_AGENT_PID=$!
# DEVEL export SSH_AUTH_SOCK="$socket"
# DEVEL echo "export SSH_AUTH_SOCK=$SSH_AUTH_SOCK" > "$run_dir/env"
# DEVEL echo "export SSH_AGENT_PID=$SSH_AGENT_PID" >> "$run_dir/env"
# DEVEL #echo "VALUE='$SSH_AUTH_SOCK $SSH_AGENT_PID'"
# if nohup ssh-agent -a "$socket" -t $life ; then
# disown $pid
# #source "$run_dir/env"
# #cat "$run_dir/env"
# export SSH_AUTH_SOCK="$socket"
# lib_log INFO "Start ssh-agent ... ($pid)"
# else
# lib_log WARN "Could not start ssh agent :("
# return 1
# fi
}
idm_ssh__agent_clean ()
{
local id=$1
local socket=$2
local pid=${3:-0}
# We should kill all agents ....
if [ "${pid}" == '0' ]; then
pid=$(grep -a "$socket" /proc/*/cmdline \
| grep -a -v 'thread-self' \
| strings -s' ' -1 \
| sed -E 's@ /proc/@ \n/proc/@g'
)
#set -x
pid="$( sed -E 's@/proc/([0-9]*)/.*@\1@' <<<"$pid" )"
fi
# Remove process
if [ ! -z "$pid" ]; then
if [ "$pid" -gt 0 ]; then
kill $pid
fi
fi
# Remove socket
if [ -f "$socket" ]; then
rm $socket
fi
unset SSH_AUTH_SOCK SSH_AGENT_PID
#lib_log INFO "ssh-agent env cleaned is now clean"
}
## Extended functions
##########################################
idm_ssh__add ()
{
local id=$1
local key=${2-}
#lib_id_is_enabled $id
lib_id_is_enabled $id
key_list=$(idm_ssh_search_private_keys "$id" "$key")
[ -n "$key_list" ] || \
idm_exit 0 WARN "No keys found"
lib_log INFO "Adding keys:"
xargs -n 1 <<<$key_list | sed "s:$HOME:~:" | lib_log DUMP -
echo ""
ssh-add $key_list
}
## SSH Library
##########################################
# This function search the bests ssh key file to use matching to an ID
idm_ssh_search_private_keys ()
{
local id=$1
local key=${2-}
local maxdepth=2
if [[ ! -z "$key" ]]; then
pub_keys=$(
{
# Compat mode
find -L ~/.ssh/$id -maxdepth $maxdepth -name "${id}_*" -name '*pub' -name "*$id*" | sort
} | sort | uniq
)
else
pub_keys=$(find -L ~/.ssh/$id -maxdepth $maxdepth -name '*pub' | sort)
fi
# Get list of key
local key_list=""
while read -r pub_key; do
if [[ -z "$pub_key" ]]; then
continue
elif [[ -f "${pub_key//\.pub/.key}" ]]; then
key_list="${key_list:+$key_list\n}${pub_key//\.pub/.key}"
else
if [[ -f "${pub_key%\.pub}" ]]; then
key_list="${key_list:+$key_list\n}${pub_key%\.pub}"
else
lib_log WARN "Can't find private key of: $pub_key"
fi
fi
done <<< "$pub_keys"
echo -e "$key_list"
}
## Deprecated functions
##########################################
### DEPRECATED # Useless at this stage i guess
### DEPRECATED idm_ssh__agent_check ()
### DEPRECATED {
### DEPRECATED #set -x
### DEPRECATED local id=$1
### DEPRECATED local socket=${2:-_}
### DEPRECATED local pid=${3:-0}
### DEPRECATED
### DEPRECATED if [ "$socket" == '_' ] && [ "$pid" == '0' ] ; then
### DEPRECATED # Parameters are not valid, we assume ssh-agent is not launched at all
### DEPRECATED return 1
### DEPRECATED elif SSH_AUTH_SOCK=$socket SSH_AGENT_PID=$pid ssh-add -l &>/dev/null ; then
### DEPRECATED return 0
### DEPRECATED else
### DEPRECATED lib_log WARN "ssh-agent is not working as expected"
### DEPRECATED fi
### DEPRECATED
### DEPRECATED # Is the socket valid ?
### DEPRECATED if [ "$socket" != '_' -a ! -S "$socket" ]; then
### DEPRECATED lib_log WARN "Socket '$socket' is dead, can't recover ssh-agent"
### DEPRECATED idm_ssh__agent_clean $id $socket 0
### DEPRECATED return 1
### DEPRECATED fi
### DEPRECATED
### DEPRECATED if [ "$pid" != '0' -a "$pid" -lt 1 ]; then
### DEPRECATED local pid="$( ps aux | grep "$socket" | grep -v 'grep' | head -n 1 | awk '{ print $2 }' )" || \
### DEPRECATED pid="$( ps aux | grep "" | grep -v 'grep' | head -n 1 | awk '{ print $2 }' )" || \
### DEPRECATED {
### DEPRECATED lib_log WARN "Process ssh-agent is dead, cannot recover"
### DEPRECATED idm_ssh__agent_clean $id $socket 0
### DEPRECATED return 1
### DEPRECATED }
### DEPRECATED
### DEPRECATED # Kill all processes
### DEPRECATED lib_log DEBUG "Multiple PID founds for ssh-agent: $pid"
### DEPRECATED q=0
### DEPRECATED for p in $pid; do
### DEPRECATED return
### DEPRECATED idm_ssh__agent_clean $id $socket $pid || true
### DEPRECATED q=1
### DEPRECATED done
### DEPRECATED [ "$q" -eq 0 ] || return 1
### DEPRECATED
### DEPRECATED fi
### DEPRECATED
### DEPRECATED # Ok, now we can try to recover the things
### DEPRECATED
### DEPRECATED
### DEPRECATED # Hmm, we should not arrive here ...
### DEPRECATED lib_log WARN "ssh-agent is in a really weird state :/"
### DEPRECATED return 1
### DEPRECATED
### DEPRECATED }