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.

343 lines
9.5 KiB

  1. #!/bin/bash
  2. . /etc/shlib
  3. include common
  4. include parse
  5. include cmdline
  6. include config
  7. [[ "${BASH_SOURCE[0]}" != "${0}" ]] && SOURCED=true
  8. version=0.1
  9. desc='Manage 0k related installs'
  10. help=""
  11. is-port-open() {
  12. local host="$1" port="$2" timeout=5
  13. start="$SECONDS"
  14. debug "Testing if $host's port $2 is open ..."
  15. while true; do
  16. timeout 1 bash -c "</dev/tcp/${host}/${port}" >/dev/null 2>&1 && break
  17. sleep 0.2
  18. if [ "$((SECONDS - start))" -gt "$timeout" ]; then
  19. return 1
  20. fi
  21. done
  22. }
  23. resolve() {
  24. local ent hostname="$1"
  25. debug "Resolving $1 ..."
  26. if ent=$(getent ahosts "$hostname"); then
  27. ent=$(echo "$ent" | egrep ^"[0-9]+.[0-9]+.[0-9]+.[0-9]+\s+" | \
  28. head -n 1 | awk '{ print $1 }')
  29. debug " .. resolved $1 to $ent."
  30. echo "$ent"
  31. else
  32. debug " .. couldn't resolve $1."
  33. return 1
  34. fi
  35. }
  36. set_errlvl() { return "${1:-1}"; }
  37. export master_pid=$$
  38. ssh:open() {
  39. local hostname ssh_cmd ssh_options
  40. ssh_cmd=(ssh)
  41. ssh_options=()
  42. while [ "$#" != 0 ]; do
  43. case "$1" in
  44. "--stdin-password")
  45. ssh_cmd=(sshpass "${ssh_cmd[@]}")
  46. ;;
  47. -o)
  48. ssh_options+=("$1" "$2")
  49. shift
  50. ;;
  51. *)
  52. [ -z "$hostname" ] && hostname="$1" || {
  53. err "Surnumerous positional argument '$1'. Expecting only hostname."
  54. return 1
  55. }
  56. ;;
  57. esac
  58. shift
  59. done
  60. "${ssh_cmd[@]}" -o ControlPath=/tmp/ssh-control-master-${master_pid} \
  61. -o ControlMaster=auto -o ControlPersist=900 \
  62. -o ConnectTimeout=5 -o "StrictHostKeyChecking=no" \
  63. "${ssh_options[@]}" \
  64. "$hostname" "$@" -- true || {
  65. err Failed: ssh -o ControlPath=/tmp/ssh-control-master-${master_pid} \
  66. -o ControlMaster=auto -o ControlPersist=900 \
  67. "$hostname" "$@" -- true
  68. return 1
  69. }
  70. trap_add EXIT,INT 'ssh:quit "$hostname"'
  71. }
  72. ssh:open-try() {
  73. local opts hostnames
  74. opts=()
  75. hostnames=()
  76. while [ "$#" != 0 ]; do
  77. case "$1" in
  78. -o)
  79. opts+=("$1" "$2")
  80. shift
  81. ;;
  82. *)
  83. hostnames+=("$1")
  84. ;;
  85. esac
  86. shift
  87. done
  88. password=''
  89. for host in "${hostnames[@]}"; do
  90. debug "Trying $host with publickey."
  91. ssh:open -o PreferredAuthentications=publickey \
  92. "${opts[@]}" \
  93. "$host" >/dev/null 2>&1 && {
  94. echo "$host"$'\n'"$password"$'\n'
  95. return 0
  96. }
  97. debug " .. failed connecting to $host with publickey."
  98. done
  99. local times=0 password
  100. while [ "$((++times))" -le 3 ]; do
  101. read -sp "$HOST's password: " password
  102. errlvl="$?"
  103. echo >&2
  104. if [ "$errlvl" -gt 0 ]; then
  105. exit 1
  106. fi
  107. for host in "${hostnames[@]}"; do
  108. debug "Trying $host with password ($times/3)"
  109. echo "$password" | ssh:open -o PreferredAuthentications=password \
  110. --stdin-password \
  111. "${opts[@]}" \
  112. "$host" >/dev/null 2>&1 && {
  113. echo "$host"$'\n'"$password"$'\n'
  114. return 0
  115. }
  116. debug " .. failed connecting to $host with password."
  117. done
  118. err "login failed. Try again... ($((times+1))/3)"
  119. done
  120. return 1
  121. }
  122. ssh:run() {
  123. local hostname="$1" ssh_options cmd
  124. shift
  125. ssh_options=()
  126. cmd=()
  127. while [ "$#" != 0 ]; do
  128. case "$1" in
  129. "--")
  130. shift
  131. cmd+=("$@")
  132. break
  133. ;;
  134. *)
  135. ssh_options+=("$1")
  136. ;;
  137. esac
  138. shift
  139. done
  140. #echo "$DARKCYAN$hostname$NORMAL $WHITE\$$NORMAL" "$@"
  141. debug "Running cmd: ${cmd[@]}"
  142. for arg in "${cmd[@]}"; do
  143. debug "$arg"
  144. done
  145. {
  146. {
  147. ssh -o ControlPath=/tmp/ssh-control-master-${master_pid} \
  148. -o ControlMaster=auto -o ControlPersist=900 \
  149. -o "StrictHostKeyChecking=no" \
  150. "$hostname" "${ssh_options[@]}" -- "${cmd[@]}"
  151. } 3>&1 1>&2 2>&3 | sed -r "s/^/$DARKCYAN$hostname$NORMAL $DARKRED\!$NORMAL /g"
  152. set_errlvl "${PIPESTATUS[0]}"
  153. } 3>&1 1>&2 2>&3
  154. }
  155. ssh:quit() {
  156. local hostname="$1"
  157. shift
  158. ssh -o ControlPath=/tmp/ssh-control-master-${master_pid} \
  159. -o ControlMaster=auto -o ControlPersist=900 -O exit \
  160. "$hostname" 2>/dev/null
  161. }
  162. is_ovh_domain_name() {
  163. local domain="$1"
  164. [[ "$domain" == *.ovh.net ]] && return 0
  165. [[ "$domain" == "ns"*".ip-"*".eu" ]] && return 0
  166. return 1
  167. }
  168. is_ovh_hostname() {
  169. local domain="$1"
  170. [[ "$domain" =~ ^vps-[0-9a-f]*$ ]] && return 0
  171. [[ "$domain" =~ ^vps[0-9]*$ ]] && return 0
  172. return 1
  173. }
  174. [ "$SOURCED" ] && return 0
  175. ##
  176. ## Command line processing
  177. ##
  178. cmdline.spec.gnu
  179. cmdline.spec.reporting
  180. cmdline.spec.gnu vps-setup
  181. cmdline.spec::cmd:vps-setup:run() {
  182. : :posarg: HOST 'Target host to check/fix ssh-access'
  183. depends sshpass shyaml
  184. KEY_PATH="ssh-access.public-keys"
  185. local keys=$(config get-value -y "ssh-access.public-keys") || true
  186. if [ -z "$keys" ]; then
  187. err "No ssh publickeys configured in config file."
  188. echo " Looking for keys in ${WHITE}${KEY_PATH}${NORMAL}" \
  189. "in config file." >&2
  190. config:exists --message 2>&1 | prefix " "
  191. if [ "${PIPESTATUS[0]}" == "0" ]; then
  192. echo " Config file found in $(config:filename)"
  193. fi
  194. return 1
  195. fi
  196. local tkey=$(e "$keys" | shyaml get-type)
  197. if [ "$tkey" != "sequence" ]; then
  198. err "Value type of ${WHITE}${KEY_PATH}${NORMAL} unexpected (is $tkey, expecting sequence)."
  199. echo " Check content of $(config:filename), and make sure to use a sequence." >&2
  200. return 1
  201. fi
  202. local IP NAME keys host_pass_connected
  203. if ! IP=$(resolve "$HOST"); then
  204. err "'$HOST' name unresolvable."
  205. exit 1
  206. fi
  207. NAME="$HOST"
  208. if [ "$IP" != "$HOST" ]; then
  209. NAME="$HOST ($IP)"
  210. fi
  211. if ! is-port-open "$IP" "22"; then
  212. err "$NAME unreachable or port 22 closed."
  213. exit 1
  214. fi
  215. debug "Host $IP's port 22 is open."
  216. if ! host_pass_connected=$(ssh:open-try \
  217. {root,debian}@"$HOST"); then
  218. err "Could not connect to {root,debian}@$HOST with publickey nor password."
  219. exit 1
  220. fi
  221. read-0a host password <<<"$host_pass_connected"
  222. sudo_if_necessary=
  223. if [ "$password" -o "${host%%@*}" != "root" ]; then
  224. if ! ssh:run "$host" -- sudo -n true >/dev/null 2>&1; then
  225. err "Couldn't do a password-less sudo from $host."
  226. echo " This is not yet supported."
  227. exit 1
  228. else
  229. sudo_if_necessary=sudo
  230. fi
  231. fi
  232. Section Checking access
  233. while read-0 key; do
  234. prefix="${key%% *}"
  235. if [ "$prefix" != "ssh-rsa" ]; then
  236. err "Unsupported key:"$'\n'"$key"
  237. return 1
  238. fi
  239. label="${key##* }"
  240. Elt "considering adding key ${DARKYELLOW}$label${NORMAL}"
  241. dest="/root/.ssh/authorized_keys"
  242. if ssh:run "$host" -- $sudo_if_necessary grep "\"$key\"" "$dest" >/dev/null 2>&1 </dev/null; then
  243. print_info "already present"
  244. print_status noop
  245. Feed
  246. else
  247. if echo "$key" | ssh:run "$host" -- $sudo_if_necessary tee -a "$dest" >/dev/null; then
  248. print_info added
  249. else
  250. echo
  251. Feedback failure
  252. return 1
  253. fi
  254. Feedback success
  255. fi
  256. done < <(e "$keys" | shyaml get-values-0)
  257. Section Checking ovh hostname file
  258. Elt "Checking /etc/ovh-hostname"
  259. if ! ssh:run "$host" -- $sudo_if_necessary [ -e "/etc/ovh-hostname" ]; then
  260. print_info "creating"
  261. ssh:run "$host" -- $sudo_if_necessary cp /etc/hostname /etc/ovh-hostname
  262. ovhname=$(ssh:run "$host" -- $sudo_if_necessary cat /etc/ovh-hostname)
  263. Elt "Checking /etc/ovh-hostname: $ovhname"
  264. Feedback || return 1
  265. else
  266. ovhname=$(ssh:run "$host" -- $sudo_if_necessary cat /etc/ovh-hostname)
  267. Elt "Checking /etc/ovh-hostname: $ovhname"
  268. print_info "already present"
  269. print_status noop
  270. Feed
  271. fi
  272. if ! is_ovh_domain_name "$HOST" && ! str_is_ipv4 "$HOST"; then
  273. Section Checking hostname
  274. Elt "Checking /etc/hostname..."
  275. if [ "$old" != "$HOST" ]; then
  276. old="$(ssh:run "$host" -- $sudo_if_necessary cat /etc/hostname)"
  277. Elt "Hostname is '$old'"
  278. if is_ovh_hostname "$old"; then
  279. Elt "Hostname '$old' --> '$HOST'"
  280. print_info "creating"
  281. echo "$HOST" | ssh:run "$host" -- $sudo_if_necessary tee /etc/hostname >/dev/null &&
  282. ssh:run "$host" -- $sudo_if_necessary hostname "$HOST"
  283. Feedback || return 1
  284. else
  285. print_info "not changing"
  286. print_status noop
  287. Feed
  288. fi
  289. else
  290. print_info "already set"
  291. print_status noop
  292. Feed
  293. fi
  294. else
  295. info "Not changing domain as '$HOST' doesn't seem to be final domain."
  296. fi
  297. }
  298. cmdline::parse "$@"