diff --git a/SSH/Create-SSH-User.bash b/SSH/Create-SSH-User.bash new file mode 100755 index 0000000..fbf4549 --- /dev/null +++ b/SSH/Create-SSH-User.bash @@ -0,0 +1,271 @@ +#!/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 +} + +usage() { + # Print out usage instructions for the local script + # + # Arguments: + # None + # + # Usage: + # usage + # + # POSIX Compliant: + # Yes + # + printf "Usage: %s\n" \ + "$(basename ${0}) -i \"this is some input\" -t \"this is some more example input\" + --input | -i + Example: + --input \"this is an example input\" + --test | -t + Example: + --test \"this is more example input\"" +} + +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 + ;; + -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 2>&1 +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}"