fork 0k-charms
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

291 lines
7.4 KiB

  1. #!/bin/bash
  2. RSYNC_KEY_PATH=/etc/rsync/keys
  3. ANSI_ESC=$'\e['
  4. NORMAL="${ANSI_ESC}0m"
  5. GRAY="${ANSI_ESC}1;30m"
  6. RED="${ANSI_ESC}1;31m"
  7. GREEN="${ANSI_ESC}1;32m"
  8. YELLOW="${ANSI_ESC}1;33m"
  9. BLUE="${ANSI_ESC}1;34m"
  10. PINK="${ANSI_ESC}1;35m"
  11. CYAN="${ANSI_ESC}1;36m"
  12. WHITE="${ANSI_ESC}1;37m"
  13. DARKGRAY="${ANSI_ESC}0;30m"
  14. DARKRED="${ANSI_ESC}0;31m"
  15. DARKGREEN="${ANSI_ESC}0;32m"
  16. DARKYELLOW="${ANSI_ESC}0;33m"
  17. DARKBLUE="${ANSI_ESC}0;34m"
  18. DARKPINK="${ANSI_ESC}0;35m"
  19. DARKCYAN="${ANSI_ESC}0;36m"
  20. DARKWHITE="${ANSI_ESC}0;37m"
  21. read-0() {
  22. local eof= IFS=''
  23. while [ "$1" ]; do
  24. read -r -d '' -- "$1" || eof=1
  25. shift
  26. done
  27. [ -z "$eof" ]
  28. }
  29. col:normalize:size() {
  30. local alignment="$1" colors="$2"
  31. # Encode the associative array into a string for awk
  32. local col_colors_string=""
  33. for key in "${!col_colors[@]}"; do
  34. col_colors_string+="${key}=${col_colors[$key]} "
  35. done
  36. # Pass the string to awk
  37. awk -v alignment="$alignment" -v colors="$colors" -v colorStr="$col_colors_string" -v normal="$NORMAL" '
  38. BEGIN {
  39. # Split the color string into key-value pairs
  40. n = split(colorStr, pairs);
  41. for (i = 1; i <= n; i++) {
  42. split(pairs[i], kv, "=");
  43. col_colors[kv[1]] = kv[2];
  44. }
  45. }
  46. {
  47. # Store the entire line in the lines array
  48. lines[NR] = $0;
  49. # Split the line into fields and update max lengths
  50. split($0, fields);
  51. for (i = 1; i <= length(fields); i++) {
  52. if (length(fields[i]) > max[i]) {
  53. max[i] = length(fields[i]);
  54. }
  55. }
  56. }
  57. END {
  58. # Print lines with fields padded to max, apply color
  59. for (i = 1; i <= NR; i++) {
  60. split(lines[i], fields);
  61. line = "";
  62. for (j = 1; j <= length(fields); j++) {
  63. # Determine alignment
  64. align = substr(alignment, j, 1) == "+" ? "+" : "-";
  65. color_code = substr(colors, j, 1);
  66. color_prefix = (color_code != "-" && color_code in col_colors) ? col_colors[color_code] : "";
  67. color_suffix = (color_prefix != "") ? normal : "";
  68. # Construct line with alignment and color
  69. line = line color_prefix sprintf("%" align max[j] "s ", fields[j]) color_suffix;
  70. }
  71. print line;
  72. }
  73. }'
  74. }
  75. declare -A col_colors=(
  76. [s]=${DARKGRAY} ## s for 'slate' (actually gray)
  77. [r]=${DARKRED}
  78. [g]=${DARKGREEN}
  79. [y]=${DARKYELLOW}
  80. [b]=${DARKBLUE}
  81. [p]=${DARKPINK}
  82. [c]=${DARKCYAN}
  83. [w]=${DARKWHITE}
  84. [S]=${GRAY}
  85. [R]=${RED}
  86. [G]=${GREEN}
  87. [Y]=${YELLOW}
  88. [B]=${BLUE}
  89. [P]=${PINK}
  90. [C]=${CYAN}
  91. [W]=${WHITE}
  92. )
  93. ssh-key-ls() {
  94. local label="$1" f content
  95. shift
  96. while read-0 f; do
  97. [ -e "$f" ] || continue
  98. ident=${f##*/}
  99. if [ "$f" != "${f%.disabled}" ]; then
  100. enabled="${RED}x"
  101. ident=${ident%.disabled}
  102. else
  103. enabled="${GREEN}✓"
  104. fi
  105. ident=${ident%.pub}
  106. content=$(cat "$f") || return 1
  107. key=${content#* }
  108. key=${key% *}
  109. commentary=${content##* }
  110. type=${commentary%%@*}
  111. # printf "${DARKGRAY}..${NORMAL}%12s ${DARKPINK}%-7s${NORMAL} ${DARKCYAN}%s${NORMAL}\n" \
  112. # "${key: -12}" "${type}" "$ident"
  113. printf "%s %s %s %s\n" "…${key: -12}" "$type" "$enabled" "$ident"
  114. done < <(find "${RSYNC_KEY_PATH}"/backup/"$label" \
  115. -maxdepth 1 -mindepth 1 \
  116. \( -name \*.pub -or -name \*.pub.disabled \) -print0 | sort -z
  117. ) | col:normalize:size "----" "ysGc"
  118. }
  119. ssh-key-rm() {
  120. local label="$1" ident="$2" delete
  121. delete="${RSYNC_KEY_PATH}/backup/$label/$ident.pub"
  122. if ! [ -e "$delete" ]; then
  123. if ! [ -e "${delete}.disabled" ]; then
  124. echo "Error: key '$ident' not found." >&2
  125. return 1
  126. fi
  127. rm "${delete}.disabled"
  128. return 0
  129. fi
  130. rm "$delete"
  131. /usr/local/sbin/ssh-update-keys
  132. }
  133. ssh-key-disable() {
  134. local label="$1" ident="$2" disable
  135. disable="${RSYNC_KEY_PATH}/backup/$label/$ident.pub"
  136. if ! [ -e "$disable" ]; then
  137. if ! [ -e "${disable}.disabled" ]; then
  138. echo "Error: key '$ident' not found." >&2
  139. return 1
  140. fi
  141. echo "Already disabled." >&2
  142. return 0
  143. fi
  144. mv "${disable}" "${disable}.disabled"
  145. /usr/local/sbin/ssh-update-keys
  146. }
  147. ssh-key-enable() {
  148. local label="$1" ident="$2" enable
  149. enable="${RSYNC_KEY_PATH}/backup/$label/$ident.pub.disabled"
  150. if ! [ -e "$enable" ]; then
  151. if ! [ -e "${enable%.disabled}" ]; then
  152. echo "Error: key '$ident' not found." >&2
  153. return 1
  154. fi
  155. echo "Already enabled." >&2
  156. return 0
  157. fi
  158. mv "${enable}" "${enable%.disabled}"
  159. /usr/local/sbin/ssh-update-keys
  160. }
  161. ssh-key-get-type() {
  162. local label="$1" ident="$2" key content commentary
  163. key="${RSYNC_KEY_PATH}/backup/$label/$ident.pub"
  164. if ! [ -e "$key" ]; then
  165. echo "Error: key '$ident' not found." >&2
  166. return 1
  167. fi
  168. content=$(cat "$key") || return 1
  169. commentary=${content##* }
  170. printf "%s\n" "${commentary%%@*}"
  171. }
  172. ssh-key-add() {
  173. local label="$1" type="$2" key="$3" email="$4"
  174. [ "$type" == "ssh-rsa" ] || {
  175. echo "Error: expecting ssh-rsa key type" >&2
  176. return 1
  177. }
  178. ## ident are unique by construction (they are struct keys)
  179. ## but keys need to be also unique
  180. declare -A keys
  181. content="$type $key $email"
  182. ident="${email##*@}"
  183. target="${RSYNC_KEY_PATH}/backup/$label/$ident.pub"
  184. ## is key used already ? As key give access to a specified subdir,
  185. ## we need to make sure it is unique.
  186. for key_file in "${RSYNC_KEY_PATH}/backup/"*/*.pub; do
  187. [ -e "$key_file" ] || continue
  188. key_content=$(cat "$key_file")
  189. if [ "$type $key" == "${key_content% *}" ]; then
  190. if [ "$key_file" == "$target" ]; then
  191. echo "Provided key already present for '$ident'." >&2
  192. return 0
  193. elif [[ "$key_file" == "${RSYNC_KEY_PATH}/"*"/$label/"*.pub ]]; then
  194. type=${key_file#"${RSYNC_KEY_PATH}/"}
  195. type=${type%"/$label/"*.pub}
  196. key_ident=${key_file##*/}
  197. key_ident=${key_ident%.pub}
  198. echo "Provided key already used as $type key for '$key_ident'." >&2
  199. return 1
  200. else
  201. olabel=${key_file#"${RSYNC_KEY_PATH}/"*/}
  202. olabel=${olabel%/*.pub}
  203. echo "Specified key is already used by '$olabel' account, please pick another one." >&2
  204. return 1
  205. fi
  206. fi
  207. done
  208. mkdir -p "${target%/*}"
  209. if [ -e "$target" ]; then
  210. echo "Replacing key for '$ident'." >&2
  211. elif [ -e "${RSYNC_KEY_PATH}/"*"/"*"/$ident.pub" ]; then
  212. olabel=("${RSYNC_KEY_PATH}/"*"/"*"/$ident.pub")
  213. olabel="${olabel[0]}"
  214. olabel=${olabel#"${RSYNC_KEY_PATH}/"*/}
  215. olabel=${olabel%/*.pub}
  216. echo "ident '$ident' is already reserved by '$olabel', please pick another one." >&2
  217. return 1
  218. fi
  219. echo "$content" > "$target"
  220. /usr/local/sbin/ssh-update-keys
  221. }
  222. case "$1" in
  223. "add")
  224. shift
  225. ssh-key-add "$@"
  226. ;;
  227. "rm")
  228. shift
  229. ssh-key-rm "$@"
  230. ;;
  231. "ls")
  232. shift
  233. ssh-key-ls "$@"
  234. ;;
  235. "disable"|"enable")
  236. arg="$1"
  237. shift
  238. ssh-key-"$arg" "$@"
  239. ;;
  240. "get-type")
  241. shift
  242. ssh-key-get-type "$@"
  243. ;;
  244. *)
  245. echo "Unknown command '$1'."
  246. ;;
  247. esac