#!/bin/bash RSYNC_KEY_PATH=/etc/rsync/keys ANSI_ESC=$'\e[' NORMAL="${ANSI_ESC}0m" GRAY="${ANSI_ESC}1;30m" RED="${ANSI_ESC}1;31m" GREEN="${ANSI_ESC}1;32m" YELLOW="${ANSI_ESC}1;33m" BLUE="${ANSI_ESC}1;34m" PINK="${ANSI_ESC}1;35m" CYAN="${ANSI_ESC}1;36m" WHITE="${ANSI_ESC}1;37m" DARKGRAY="${ANSI_ESC}0;30m" DARKRED="${ANSI_ESC}0;31m" DARKGREEN="${ANSI_ESC}0;32m" DARKYELLOW="${ANSI_ESC}0;33m" DARKBLUE="${ANSI_ESC}0;34m" DARKPINK="${ANSI_ESC}0;35m" DARKCYAN="${ANSI_ESC}0;36m" DARKWHITE="${ANSI_ESC}0;37m" read-0() { local eof= IFS='' while [ "$1" ]; do read -r -d '' -- "$1" || eof=1 shift done [ -z "$eof" ] } col:normalize:size() { local alignment="$1" colors="$2" # Encode the associative array into a string for awk local col_colors_string="" for key in "${!col_colors[@]}"; do col_colors_string+="${key}=${col_colors[$key]} " done # Pass the string to awk awk -v alignment="$alignment" -v colors="$colors" -v colorStr="$col_colors_string" -v normal="$NORMAL" ' BEGIN { # Split the color string into key-value pairs n = split(colorStr, pairs); for (i = 1; i <= n; i++) { split(pairs[i], kv, "="); col_colors[kv[1]] = kv[2]; } } { # Store the entire line in the lines array lines[NR] = $0; # Split the line into fields and update max lengths split($0, fields); for (i = 1; i <= length(fields); i++) { if (length(fields[i]) > max[i]) { max[i] = length(fields[i]); } } } END { # Print lines with fields padded to max, apply color for (i = 1; i <= NR; i++) { split(lines[i], fields); line = ""; for (j = 1; j <= length(fields); j++) { # Determine alignment align = substr(alignment, j, 1) == "+" ? "+" : "-"; color_code = substr(colors, j, 1); color_prefix = (color_code != "-" && color_code in col_colors) ? col_colors[color_code] : ""; color_suffix = (color_prefix != "") ? normal : ""; # Construct line with alignment and color line = line color_prefix sprintf("%" align max[j] "s ", fields[j]) color_suffix; } print line; } }' } declare -A col_colors=( [s]=${DARKGRAY} ## s for 'slate' (actually gray) [r]=${DARKRED} [g]=${DARKGREEN} [y]=${DARKYELLOW} [b]=${DARKBLUE} [p]=${DARKPINK} [c]=${DARKCYAN} [w]=${DARKWHITE} [S]=${GRAY} [R]=${RED} [G]=${GREEN} [Y]=${YELLOW} [B]=${BLUE} [P]=${PINK} [C]=${CYAN} [W]=${WHITE} ) ssh-key-ls() { local label="$1" f content shift while read-0 f; do [ -e "$f" ] || continue ident=${f##*/} if [ "$f" != "${f%.disabled}" ]; then enabled="${RED}x" ident=${ident%.disabled} else enabled="${GREEN}✓" fi ident=${ident%.pub} content=$(cat "$f") || return 1 key=${content#* } key=${key% *} commentary=${content##* } type=${commentary%%@*} # printf "${DARKGRAY}..${NORMAL}%12s ${DARKPINK}%-7s${NORMAL} ${DARKCYAN}%s${NORMAL}\n" \ # "${key: -12}" "${type}" "$ident" printf "%s %s %s %s\n" "…${key: -12}" "$type" "$enabled" "$ident" done < <(find "${RSYNC_KEY_PATH}"/backup/"$label" \ -maxdepth 1 -mindepth 1 \ \( -name \*.pub -or -name \*.pub.disabled \) -print0 | sort -z ) | col:normalize:size "----" "ysGc" } ssh-key-rm() { local label="$1" ident="$2" delete delete="${RSYNC_KEY_PATH}/backup/$label/$ident.pub" if ! [ -e "$delete" ]; then if ! [ -e "${delete}.disabled" ]; then echo "Error: key '$ident' not found." >&2 return 1 fi rm "${delete}.disabled" return 0 fi rm "$delete" /usr/local/sbin/ssh-update-keys } ssh-key-disable() { local label="$1" ident="$2" disable disable="${RSYNC_KEY_PATH}/backup/$label/$ident.pub" if ! [ -e "$disable" ]; then if ! [ -e "${disable}.disabled" ]; then echo "Error: key '$ident' not found." >&2 return 1 fi echo "Already disabled." >&2 return 0 fi mv "${disable}" "${disable}.disabled" /usr/local/sbin/ssh-update-keys } ssh-key-enable() { local label="$1" ident="$2" enable enable="${RSYNC_KEY_PATH}/backup/$label/$ident.pub.disabled" if ! [ -e "$enable" ]; then if ! [ -e "${enable%.disabled}" ]; then echo "Error: key '$ident' not found." >&2 return 1 fi echo "Already enabled." >&2 return 0 fi mv "${enable}" "${enable%.disabled}" /usr/local/sbin/ssh-update-keys } ssh-key-get-type() { local label="$1" ident="$2" key content commentary key="${RSYNC_KEY_PATH}/backup/$label/$ident.pub" if ! [ -e "$key" ]; then echo "Error: key '$ident' not found." >&2 return 1 fi content=$(cat "$key") || return 1 commentary=${content##* } printf "%s\n" "${commentary%%@*}" } ssh-key-add() { local label="$1" type="$2" key="$3" email="$4" [ "$type" == "ssh-rsa" ] || { echo "Error: expecting ssh-rsa key type" >&2 return 1 } ## ident are unique by construction (they are struct keys) ## but keys need to be also unique declare -A keys content="$type $key $email" ident="${email##*@}" target="${RSYNC_KEY_PATH}/backup/$label/$ident.pub" ## is key used already ? As key give access to a specified subdir, ## we need to make sure it is unique. for key_file in "${RSYNC_KEY_PATH}/backup/"*/*.pub; do [ -e "$key_file" ] || continue key_content=$(cat "$key_file") if [ "$type $key" == "${key_content% *}" ]; then if [ "$key_file" == "$target" ]; then echo "Provided key already present for '$ident'." >&2 return 0 elif [[ "$key_file" == "${RSYNC_KEY_PATH}/"*"/$label/"*.pub ]]; then type=${key_file#"${RSYNC_KEY_PATH}/"} type=${type%"/$label/"*.pub} key_ident=${key_file##*/} key_ident=${key_ident%.pub} echo "Provided key already used as $type key for '$key_ident'." >&2 return 1 else olabel=${key_file#"${RSYNC_KEY_PATH}/"*/} olabel=${olabel%/*.pub} echo "Specified key is already used by '$olabel' account, please pick another one." >&2 return 1 fi fi done mkdir -p "${target%/*}" if [ -e "$target" ]; then echo "Replacing key for '$ident'." >&2 elif [ -e "${RSYNC_KEY_PATH}/"*"/"*"/$ident.pub" ]; then olabel=("${RSYNC_KEY_PATH}/"*"/"*"/$ident.pub") olabel="${olabel[0]}" olabel=${olabel#"${RSYNC_KEY_PATH}/"*/} olabel=${olabel%/*.pub} echo "ident '$ident' is already reserved by '$olabel', please pick another one." >&2 return 1 fi echo "$content" > "$target" /usr/local/sbin/ssh-update-keys } case "$1" in "add") shift ssh-key-add "$@" ;; "rm") shift ssh-key-rm "$@" ;; "ls") shift ssh-key-ls "$@" ;; "disable"|"enable") arg="$1" shift ssh-key-"$arg" "$@" ;; "get-type") shift ssh-key-get-type "$@" ;; *) echo "Unknown command '$1'." ;; esac