#!/bin/bash echo_rgb() { # Echo a colored string to the terminal based on rgb values # # Positional Arguments: # # message # - The message to be printed to stdout # red # - The red value from 0 to 255 # green # - The green value from 0 to 255 # blue # - The blue value from 0 to 255 # # Usage: # echo_rgb "Yep" 10 8 30 # # POSIX Compliant: # N/A # local red local green local blue local input input="${1}" red="${2}" green="${3}" blue="${4}" printf "\e[0;38;2;%s;%s;%sm%s\e[m\n" "${red}" "${green}" "${blue}" "${input}" } log() { # Print a message and send it to stdout or stderr depending upon log level, also configurable with debug etc. # # Arguments: # level # - The log level, defined within a case check in this function # message # - The info message # line_number # - The line number of the calling function (${LINNO}) # # Usage: # log "info" "Could not find that directory" # # POSIX Compliant: # Yes # # Set debug status depending if a global debug variable has been set to either 1 or 0 local debug if [ ${DEBUG} ]; then debug=${DEBUG} else debug=0 fi local FORMAT FORMAT="[$(echo_rgb "$(date +%Y-%m-%dT%H:%M:%S)" 180 140 255)]" # Convert the level to uppercase local level level=$(echo "${1}" | tr '[:lower:]' '[:upper:]') local message message="${2}" case "${level}" in INFO) # Output all info log levels to stdout printf "${FORMAT}[$(echo_rgb "INFO" 0 140 255)] %s\n" "${message}" >&1 return 0 ;; WARN | WARNING) # Output all info log levels to stdout printf "${FORMAT}[$(echo_rgb "WARNING" 255 255 0)] %s\n" "${message}" >&1 return 0 ;; DEBUG) [[ ${debug} == 0 ]] && return printf "${FORMAT}[$(echo_rgb "DEBUG" 0 160 110)] %s\n" "${message}" >&1 return 0 ;; ERROR) # Output all error log levels to stderr printf "${FORMAT}[$(echo_rgb "ERROR" 255 0 0)] %s\n" "${message}" >&2 return 0 ;; # Further log levels can be added by extending this switch statement with more comparisons *) # Default case, no matches # Returns non-zero code as an improper log option was passed, this helps with using `set -e` printf "${FORMAT}[ERROR] %s\n" "Invalid log level passed, received level \"${level}\" with message \"${message}\"" >&2 return 1 ;; esac } arg_required() { echo_rgb "${1}" 255 183 0 } arg_optional(){ echo_rgb "${1}" 117 255 255 } arg_description(){ echo_rgb "${1}" 220 190 255 } usage() { # Print out usage instructions for the local script # # Arguments: # None # # Usage: # usage # # POSIX Compliant: # Yes # printf "$(echo_rgb "Usage:" 50 220 50) %s\n" \ "$(echo_rgb "$(basename ${0})" 180 255 180) $(arg_required "-U") $(arg_required "-H") $(arg_optional "[OPTIONS]") $(arg_required "REQUIRED") $(arg_optional "OPTIONAL") $(arg_required "-U") | $(arg_required "--user") $(arg_description \ "Takes in a username that is to be created on the remote host Example: --user User1") $(arg_required "-H") | $(arg_required "--host-name") $(arg_description \ "The SSH hostname for the given host, MUST exist within your ssh configuration; typically located at ~/.ssh/config Example: --host-name gitlab.orion-technologies.io") $(arg_optional "-L") | $(arg_optional "--login-user") $(arg_description \ "The user used to login to your host, must be a privileged sudo user. Default user is root. Example: --login-user Admin") $(arg_optional "-K") | $(arg_optional "--key-type") $(arg_description \ "The SSH key type to generate, passed to ssh-keygen's -t flag. Example: --key-type rsa") $(arg_optional "-B") | $(arg_optional "--bits") $(arg_description \ "The number of bits used to generate the given key, default is set at 512. Example: --bits 2048")" } ssh_user_to_create="" ssh_host_name="" ssh_key_type="ed25519" ssh_key_bits=512 ssh_login_user="root" parse_args() { # Parse input arguments # # Arguments: # Consult the `usage` function # # Usage: # parse_args "$@" # - All arguments should be ingested by parse_args first for variable setting # # POSIX Compliant: # Yes # while :; do case ${1} in -h | -\? | --help) usage # Display a usage synopsis. exit ;; --) # End of all options. break ;; -U | --user) ssh_user_to_create="${2}" [[ -z "${ssh_user_to_create}" ]] && log "error" "No argument provided for ${1}" && exit 1 ;; -K | --key-type) ssh_key_type="${2}" [[ -z "${ssh_key_type}" ]] && log "error" "No argument provided for ${1}" && exit 1 ;; -B | --bits) ssh_key_bits="${2}" [[ -z "${ssh_key_bits}" ]] && log "error" "No argument provided for ${1}s" && exit 1 [[ ! "${ssh_key_bits}" =~ [0-9] ]] && log "error" "--bits must be a whole number (int), received ${ssh_key_bits}" && exit 1 ;; -H | --host-name) ssh_host_name="${2}" [[ -z "${ssh_host_name}" ]] && log "error" "No argument provided for ${1}" && exit 1 ;; -L | --login-user) ssh_login_user="${2}" [[ -z "${ssh_login_user}" ]] && log "error" "No argument provided for ${1}" && exit 1 ;; -?*) printf 'Unknown option: %s\n' "$1" >&2 usage exit 1 ;; *) # Default case: No more options, so break out of the loop. break ;; esac shift 2 done } parse_args "$@" [[ -z "${ssh_user_to_create}" ]] && log "error" "User may not be empty" && exit 1 [[ -z "${ssh_host_name}" ]] && log "error" "--host-name may not be empty" && exit 1 SSH_KEY_DIRECTORY="${HOME}/.ssh/keys/${ssh_host_name}/" log "info" "Creating directory ${SSH_KEY_DIRECTORY} for the ssh key if it doesn't exist" mkdir -p "${SSH_KEY_DIRECTORY}" > /dev/null SSH_KEY_FILE="${SSH_KEY_DIRECTORY}/${ssh_user_to_create}-${ssh_key_type}" [[ -f "${SSH_KEY_FILE}" ]] && log "error" "${SSH_KEY_FILE} already exists! This may lead to major errors, check your SSH configuration and remove the SSH entry as well as the SSH key file or create a different user if you wish to continue." && exit 1 log "info" "Generating SSH key file for ${ssh_user_to_create}@${ssh_host_name} at ${SSH_KEY_FILE}" ssh-keygen -b 512 -t ed25519 -f "${SSH_KEY_FILE}" -N "" > /dev/null || exit "${?}" chmod 600 "${SSH_KEY_FILE}" > /dev/null log "info" "SSH key successfully created" PUB_KEY_CONTENTS="$(cat "${SSH_KEY_FILE}.pub")" log "info" "Logging into remote server as ${ssh_login_user}@${ssh_host_name} to create ${ssh_user_to_create} and directories" ssh "${ssh_login_user}"@"${ssh_host_name}" > /dev/null <<__EOF__ sudo useradd ${ssh_user_to_create} mkdir -p /home/${ssh_user_to_create}/.ssh echo ${PUB_KEY_CONTENTS} >> /home/${ssh_user_to_create}/.ssh/authorized_keys chown -R ${ssh_user_to_create}:${ssh_user_to_create} /home/${ssh_user_to_create}/.ssh __EOF__ if [[ "${?}" -ne "0" ]]; then log "error" "Unable to login to remote server with ${ssh_login_user}@${ssh_host_name}" log "error" "Verify the correct user and host name have been provided and that an SSH configuration exists for the pair in your SSH configuration" rm "${SSH_KEY_FILE}" exit 1 fi SSH_CONFIG_FILE="${HOME}/.ssh/config" log "info" "Finished creating ${ssh_user_to_create}, generating ssh configuration for ${SSH_CONFIG_FILE}" SPACE_PADDING=" " SSH_LINES_TO_ADD="${SPACE_PADDING}Match user ${ssh_user_to_create} host ${ssh_host_name} ${SPACE_PADDING}${SPACE_PADDING}IdentityFile ${SSH_KEY_FILE}" log "info" "Generated new lines for SSH configuration: ${SSH_LINES_TO_ADD}" SSH_BACKUP_FILE="${SSH_CONFIG_FILE}.bak.$(date +%Y-%m-%dT%H:%M:%S)" log "info" "Backing up ${SSH_CONFIG_FILE} to ${SSH_BACKUP_FILE}" cp "${SSH_CONFIG_FILE}" "${SSH_BACKUP_FILE}" > /dev/null 2>&1 log "info" "Searching for a Host entry for ${ssh_host_name} within ${SSH_CONFIG_FILE}" # Remove case sensitivity from string matching shopt -s nocasematch should_write=0 while IFS= read -r line; do printf "%s\n" "${line}" if [[ "${line}" = *"HostName ${ssh_host_name}"* ]]; then should_write=1 fi if [[ "${should_write}" -eq "1" ]]; then printf "%s\n" "${SSH_LINES_TO_ADD}" should_write=0 fi done < "${SSH_CONFIG_FILE}" > "ssh_conf.temp" && mv "ssh_conf.temp" "${SSH_CONFIG_FILE}" log "info" "Successfully wrote the new SSH configuration, login with ${ssh_user_to_create}@${ssh_host_name}"