From 40deeaac174b823b3905a63cc2c55001995aa2b8 Mon Sep 17 00:00:00 2001 From: Pratik Tripathy <> Date: Tue, 17 Dec 2024 22:36:30 +0530 Subject: [PATCH] feat: Create new user - Log stuff to a file and to console - Create user - Username from parameter if provided else generate it - Revert user creation if it failed midway --- init-linux-harden.sh | 1416 ++++-------------------------------------- 1 file changed, 104 insertions(+), 1312 deletions(-) mode change 100644 => 100755 init-linux-harden.sh diff --git a/init-linux-harden.sh b/init-linux-harden.sh old mode 100644 new mode 100755 index fe756cd..311cd72 --- a/init-linux-harden.sh +++ b/init-linux-harden.sh @@ -1,1351 +1,143 @@ -#!/bin/bash +#!/bin/sh + +# TODO: Setup docker for testing across OSes +# TODO: Make it posix as POSIX compliant as possible - freebsd feasible??? +# TODO: Things from https://github.com/imthenachoman/How-To-Secure-A-Linux-Server SCRIPT_NAME=linux_init_harden -SCRIPT_VERSION=1.0 +SCRIPT_VERSION=2.0 +LOGFILE_NAME="${SCRIPT_NAME}_$(date '+%Y-%m-%d_%H-%M-%S').log" -LOGFILE=/tmp/"$SCRIPT_NAME"_v"$SCRIPT_VERSION".log -# Reset previous log file -TS=$(date '+%d_%m_%Y-%H_%M_%S') -echo "Starting $0 - $TS" > "$LOGFILE" -BACKUP_EXTENSION='.'$TS"_bak" +# Global variables for username and password +USERNAME="" +USER_PASSWORD="" -# Colors -CSI='\033[' -CEND="${CSI}0m" -CRED="${CSI}1;31m" -CGREEN="${CSI}1;32m" - - -############################################################## -# Usage -############################################################## - -# Script takes arguments as follows -# linux_init_harden -username pratik --resetrootpwd -# linux_init_harden -u pratik --resetrootpwd -# linux_init_harden -username pratik --resetrootpwd -q -hide - -function usage() { - if [ -n "$1" ]; then - echo "" - echo -e "${CRED}$1${CEND}\n" - fi - - echo "Usage: sudo bash $0 [-u|--username username] [-r|--resetrootpwd] [--defaultsourcelist]" - echo " -u, --username Username for your server (If omitted script will choose an username for you)" - echo " -r, --resetrootpwd Reset current root password" - echo " -hide, --hide-credentials Credentials will hidden from screen and can ONLY be found in the logfile" - echo " eg: tail -n 20 logfile" - echo " -d, --defaultsourcelist Updates /etc/apt/sources.list to download software from debian.org" - echo " -ou, --only-user Only creates the user and its SSH authorizations" - echo " NOTE: -r, -d would be ignored" - - echo "" - echo "Example: bash ./$SCRIPT_NAME.sh --username myuseraccount --resetrootpwd" - printf "\\nBelow restrictions apply to usernames - \\n" - printf "%2s - [a-zA-Z0-9] [-] [_] are allowed\\n%2s - NO special characters.\\n%2s - NO spaces.\\n" " " " " " " -} - - -############################################################## -# Basic checks before starting -############################################################## - -# No root - no good -[ "$(id -u)" != "0" ] && { - usage "ERROR: You must be root to run this script.\\nPlease login as root and execute the script again." - exit 1 -} - -function os_not_supported() { - printf "This script only supports: \\n\\tDebian 9, 10, and 11\\n" - printf "\\tUbuntu 16.04, 18.04, 18.10, 20.04, and 21.04\\n" - printf "Your OS is NOT supported.\\n" -} - -function new_os_version_warning(){ - echo "${1} version ${2} is not tested. Continuing is NOT RECOMMENDED." - read -p "Continue (NOT RECOMMENDED)? (Y/N): " confirm && [[ $confirm == [yY] || $confirm == [yY][eE][sS] ]] || exit 1 -} - -# Fail-fast: No apt - no good -if ! command -v apt-get >/dev/null; then - os_not_supported - exit 1 -fi - -# Check supported OSes -if [ -f /etc/os-release ]; then - . /etc/os-release - OS=$ID - VER=$VERSION_ID - CODE_NAME=$VERSION_CODENAME -else - os_not_supported - exit 1 -fi - -case "$OS" in - debian) - # If the versions are not 9, 10, 11 - # warn user and ask them to proceed with caution - DEB_VER_STR=$CODE_NAME - if ((VER >= 9 && VER <= 11)); then - new_os_version_warning - fi - ;; - ubuntu) - # If the versions are not 16.04, 18.04, 18.10, 20.04. 21.04 - # warn user and ask them to proceed with caution - UBT_VER_STR=$CODE_NAME - if [[ "$VER" != "16.04" ]] && [[ "$VER" != "18.04" ]] && [[ "$VER" != "18.10" ]] && [[ "$VER" != "20.04" ]] && [[ "$VER" != "21.04" ]]; then - new_os_version_warning "${OS}" "${VER}" - fi - ;; - *) - os_not_supported - exit 1 - ;; -esac - -################################## -# Parse script arguments -################################## - -# defaults -AUTO_GEN_USERNAME="y" -RESET_ROOT_PWD="n" -DEFAULT_SOURCE_LIST="n" -QUIET="n" -HIDE_CREDENTIALS="n" -USER_CREATION_ALONE="n" - -while [[ "$#" -gt 0 ]]; do - case $1 in - -u|--username) - # validate username - if [[ $(echo "$2" | grep -Pnc '^[a-zA-Z0-9_-]+$') -eq 0 ]]; then - usage "Invalid characters in the user name." - exit 1 - elif [[ $(getent passwd "$2" | wc -l) -gt 0 ]]; then - echo - echo -e "${CRED}User name ($2) already exists.${CEND}\n" - exit 1 +# Parse command line arguments +parse_args() { + while [ "$#" -gt 0 ]; do + case "$1" in + -u | --username) + # Validate username format + if [ -n "$2" ] && echo "$2" | grep -qE '^[a-zA-Z][a-zA-Z0-9_-]*$'; then + USERNAME="$2" + shift 2 else - AUTO_GEN_USERNAME="n" - NORM_USER_NAME="$2" + console_log "Error" "Invalid username format. Must start with a letter and contain only alphanumeric characters, _, or -" + exit 1 fi - - shift - shift - ;; - -ou|--only-user) - USER_CREATION_ALONE="y" - shift - ;; - -r|--resetrootpwd) - RESET_ROOT_PWD="y" - shift - ;; - -d|--defaultsourcelist) - DEFAULT_SOURCE_LIST="y" - shift - ;; - -q|--quiet|--nowait|--noprompt) - QUIET="y" - shift - ;; - -hide|--hide-credentials) - HIDE_CREDENTIALS="y" - shift - ;; - -h|--help) - echo - usage - echo - exit 0 - shift ;; *) - usage "Unknown parameter passed: $1" "h" - exit 1 - shift shift ;; - esac -done - - -############################################################## -# Display what the script does -############################################################## - -clear - -cat </dev/null 2>&1; then + console_log "Error" "User $USERNAME already exists" + return 1 + fi -############################################################## -# Op Logs -############################################################## + # Generate a 15-character random password + USER_PASSWORD=$(head -c 12 /dev/urandom | base64 | tr -dc "[:alnum:]" | head -c 15) -function file_log(){ - printf "%s - %s\\n" "$(date '+%d-%b-%Y %H:%M:%S')" "$1" >> "$LOGFILE" -} + # Create user with the generated password + console_log "Info" "Creating user $USERNAME" + if (echo "$USERNAME:$USER_PASSWORD" | chpasswd) && + adduser "$USERNAME" --gecos ",,," --disabled-password && + usermod -aG sudo "$USERNAME"; then -function log_step_status() { - local EVENT=$1 - local RESULT=$2 + # Log user creation details + file_log "User created: $USERNAME" + file_log "User password: $USER_PASSWORD" + console_log "Success" "User $USERNAME created successfully" - if [[ "$RESULT" = "SUCCESSFUL" ]]; then - printf "\r%33s %7s [${CGREEN}${RESULT}${CEND}]\\n" "$EVENT" " " - file_log "${EVENT} - ${RESULT}" - elif [[ "$RESULT" = "FAILED" ]]; then - printf "\r%33s %7s [${CRED}${RESULT}${CEND}]\\n" "$EVENT" " " - file_log "${EVENT} - ${RESULT}" - elif [[ "$RESULT" = "NO-OP" ]]; then - printf "\r%33s %7s [${RESULT}]\\n" "$EVENT" " " - file_log "${EVENT} - No operation done. Check above for details..." + return 0 else - printf "%33s %7s [${CRED}..${CEND}]" "$EVENT" " " - file_log "${EVENT} - begin..." - fi -} - -function log_op_rev_status(){ - printf "${CRED}" - log_step_status "$1" "$2" - printf "${CEND}" -} - -function log_ops_finish (){ - local purpose=$1 - local status=$2 - local value=$3 - - if [[ $status -eq 0 ]]; then - echo "${purpose}: Did not start this operation. See log above." 2>> ${LOGFILE} >&2 - value="[${CGREEN}--NO_OP--${CEND}]" - elif [[ $status -eq 2 ]]; then - echo "${purpose}: ${value}" 2>> ${LOGFILE} >&2 - value="[${CGREEN}${value}${CEND}]" - elif [[ $status -eq 1 ]] || [[ $status -eq 3 ]]; then - echo "${purpose}: ERROR. See log above." 2>> ${LOGFILE} >&2 - value="${CRED}--ERROR--${CEND}" - fi - - if [[ $HIDE_CREDENTIALS == "n" ]]; then - horizontal_fill "$CVERTICAL" 1 - printf "%23s:%3s%-54s" "$purpose" " " "$(echo -e "$value")" - line_fill "$CVERTICAL" 1 - fi -} - -function log_ops_finish_file_contents() { - local file_type=$1 - local file_location=$2 - - file_log "$file_type" - cat "$file_location" 2>> "$LOGFILE" >&2 - - if [[ $HIDE_CREDENTIALS == "n" ]]; then - echo - center_reg_text "$file_type" - echo - printf "${CGREEN}" - cat "$file_location" - printf "${CEND}" - fi -} - -function log_revert_error(){ - log_step_status "$1" "FAILED" - echo - center_err_text "!!! Error restoring changes !!!" - center_err_text "!!! You may have to manually fix this !!!" - center_err_text "!!! Check the log file for details !!!" - echo -} - - -############################################################## -# Op Error Handling -############################################################## -# Remember to reset exit_code at end of EACH revert fn - -function revert_create_user(){ - file_log "Reverting New User Creation..." - - # Remove user and its home directory only if user was created - if [[ $(getent passwd "$NORM_USER_NAME" | wc -l) -gt 0 ]]; then - { - file_log "Deleting user ${NORM_USER_NAME} ..." - deluser "$NORM_USER_NAME" - - file_log "Deleting user ${NORM_USER_NAME} home directory and all its content ..." - rm -rf /home/"${NORM_USER_NAME:?}" - set_exit_code $? - } 2>> "$LOGFILE" >&2 - set_exit_code $? - fi - - if [[ $exit_code -eq 0 ]]; then - log_op_rev_status "Reverting - New User Creation" "SUCCESSFUL" - else - file_log "Error Code - ${exit_code}" - log_revert_error "Reverting - New User Creation" - fi -} - -function revert_create_ssh_key(){ - file_log "Reverting SSH Key Generation..." - revert_create_user - set_exit_code $? - - # Since all SSH files are created inside the user's home directory - # There is nothing to revert if username is deleted - - if [[ $exit_code -eq 0 ]]; then - log_op_rev_status "Reverting - SSH Key Generation" "SUCCESSFUL" - else - file_log "Error Code - ${exit_code}" - log_revert_error "Reverting - SSH Key Generation" - fi -} - -function revert_secure_authorized_key(){ - file_log "Reverting SSH Key Authorizations..." - - if [[ -f "$SSH_DIR"/authorized_keys ]]; then - { - file_log "Removing the immutable flag from every file in /home/${NORM_USER_NAME}/.ssh/ directory ..." - chattr -i "$SSH_DIR"/* - set_exit_code $? - - # Nothing else to restore since we are going to delete the user & its directories anyways - # All the files created/changed by the script are inside /home/[username]/.ssh directory only - } 2>> "$LOGFILE" >&2 - fi - - # Can remove the user only AFTER immutable attributes on authorized_keys is removed - revert_create_ssh_key - - if [[ $exit_code -eq 0 ]]; then - log_op_rev_status "Reverting - SSH Key Authorization" "SUCCESSFUL" - else - file_log "Error Code - ${exit_code}" - log_revert_error "Reverting - SSH Key Authorization" - fi -} - -function revert_source_list_changes(){ - reset_exit_code - - file_log "Reverting Source_list Changes..." - - unalias cp &>/dev/null - - if [[ -f /etc/apt/sources.list"${BACKUP_EXTENSION}" ]]; then - file_log "Restoring /etc/apt/sources.list${BACKUP_EXTENSION} into /etc/apt/sources.list ..." - cp -rf /etc/apt/sources.list"${BACKUP_EXTENSION}" /etc/apt/sources.list 2>> "$LOGFILE" >&2 - set_exit_code $? - fi - - SOURCE_FILES_BKP=(/etc/apt/source*/*.list"$BACKUP_EXTENSION") - if [[ ${#SOURCE_FILES_BKP[@]} -gt 0 ]]; then - for file in "${SOURCE_FILES_BKP[@]}"; - do - file_log "Restoring ${file} into ${file//$BACKUP_EXTENSION/} ..." - cp -rf "$file" "${file//$BACKUP_EXTENSION/}" 2>> "$LOGFILE" >&2 - set_exit_code $? - done - fi - - if [[ $exit_code -eq 0 ]]; then - log_op_rev_status "Reverting - Source_list Changes" "SUCCESSFUL" - else - file_log "Error Code - ${exit_code}" - log_revert_error "Reverting - Source_list Changes" - fi - - reset_exit_code -} - -function revert_root_pass_change(){ - reset_exit_code - - echo - center_err_text "Changing root password failed..." - center_err_text "Your earlier root password remains VALID" - center_err_text "Script will continue to next step" -} - -function revert_config_UFW(){ - reset_exit_code - file_log "Reverting UFW Configuration..." - - ufw disable 2>> "$LOGFILE" >&2 - set_exit_code $? - - if [[ $exit_code -eq 0 ]]; then - log_op_rev_status "Reverting - UFW Configuration" "SUCCESSFUL" - else - file_log "Error Code - ${exit_code}" - log_revert_error "Reverting - UFW Configuration" - fi - - reset_exit_code -} - -function revert_config_fail2ban(){ - reset_exit_code - - file_log "Reverting Fail2ban Config..." - - unalias cp &>/dev/null - - if [[ -f /etc/fail2ban/jail.local"$BACKUP_EXTENSION" ]]; then - # If /etc/fail2ban/jail.local/_bkp exists then this is NOT the 1st time script is run - # So, you would probaly want to get the last existing jail.local file back - file_log "Restoring /etc/fail2ban/jail.local${BACKUP_EXTENSION} into /etc/fail2ban/jail.local" - cp -rf /etc/fail2ban/jail.local"$BACKUP_EXTENSION" /etc/fail2ban/jail.local 2>> "$LOGFILE" >&2 - set_exit_code $? - else - # If /etc/fail2ban/jail.local/_bkp does NOT exists then this IS the 1st time script is run - # You probably do NOT want the jail.local > which might be corrupted > which is why you are here - file_log "Removing /etc/fail2ban/jail.local" - rm /etc/fail2ban/jail.local 2>> "$LOGFILE" >&2 - set_exit_code $? - fi - - if [[ -f /etc/fail2ban/jail.d/defaults-debian.conf"$BACKUP_EXTENSION" ]]; then - file_log "Restoring /etc/fail2ban/jail.d/defaults-debian.conf${BACKUP_EXTENSION} into /etc/fail2ban/jail.d/defaults-debian.conf" - cp -rf /etc/fail2ban/jail.d/defaults-debian.conf"$BACKUP_EXTENSION" /etc/fail2ban/jail.d/defaults-debian.conf 2>> "$LOGFILE" >&2 - set_exit_code $? - fi - - file_log "Stopping fail2ban service ..." - { - set_exit_code $(service_action_and_chk_error "fail2ban" "stop") - } 2>> "$LOGFILE" >&2 - - if [[ $exit_code -eq 0 ]]; then - log_op_rev_status "Reverting - Fail2ban Config" "SUCCESSFUL" - else - file_log "Error Code - ${exit_code}" - log_revert_error "Reverting - Fail2ban Config" - fi - - reset_exit_code -} - -function revert_software_installs(){ - reset_exit_code - - echo - center_err_text "Error while installing softwares" - center_err_text "This may be a false-alarm" - center_err_text "Script will continue to next step" - file_log "Installing software failed..." - file_log "This is NOT a catastrophic error" -} - -function revert_schedule_updates() { - reset_exit_code - file_log "Reverting Daily Update Download..." - - rm "$dailycron_filename" - set_exit_code $? - - if [[ $exit_code -eq 0 ]]; then - log_op_rev_status "Reverting - Daily Update Download" "SUCCESSFUL" - else - file_log "Error Code - ${exit_code}" - log_revert_error "Reverting - Daily Update Download" - fi - - reset_exit_code -} - -function revert_ssh_only_login(){ - revert_secure_authorized_key - - if [[ $DEFAULT_SOURCE_LIST = "y" ]]; then - revert_source_list_changes - fi - revert_config_UFW - revert_config_fail2ban - revert_schedule_updates - - file_log "Reverting SSH-only Login..." - - if [[ -f /etc/ssh/sshd_config"$BACKUP_EXTENSION" ]]; then - unalias cp &>/dev/null - - file_log "Restoring /etc/ssh/sshd_config${BACKUP_EXTENSION} into /etc/ssh/sshd_config ..." - cp -rf /etc/ssh/sshd_config"$BACKUP_EXTENSION" /etc/ssh/sshd_config 2>> "$LOGFILE" >&2 - set_exit_code $? - fi - - file_log "Restarting ssh service ..." - { - set_exit_code $(service_action_and_chk_error "sshd" "restart") - - if [[ $exit_code -eq 0 ]]; then - false - fi - } || { - # Because Ubuntu 14.04 does not have sshd - set_exit_code $(service_action_and_chk_error "ssh" "restart") - } 2>> "$LOGFILE" >&2 - - if [[ $exit_code -eq 0 ]]; then - log_op_rev_status "Reverting - SSH-only Login" "SUCCESSFUL" - else - file_log "Error Code - ${exit_code}" - log_revert_error "Reverting - SSH-only Login" - fi - - reset_exit_code -} - -function revert_everything_and_exit() { - echo - center_err_text "!!! ERROR OCCURED DURING OPERATION !!!" - center_err_text "!!! Reverting changes !!!" - center_err_text "Please look at $LOGFILE for details" - echo - - file_log "Starting revert operation..." - - reset_exit_code - - if [[ $1 = "${STEP_TEXT[0]}" ]]; then + console_log "Error" "Failed to create user $USERNAME" + # Attempt to cleanup if user creation fails revert_create_user - elif [[ $1 = "${STEP_TEXT[1]}" ]]; then - revert_create_ssh_key - elif [[ $1 = "${STEP_TEXT[2]}" ]]; then - revert_secure_authorized_key - elif [[ $1 = "${STEP_TEXT[3]}" ]]; then - revert_ssh_only_login - fi - - center_reg_text "Total execution time - ${SECONDS}s" - - exit 1; -} - - -############################################################## -# Op-setup & Utility Functions -############################################################## -exit_code=0 - -# Step Status -# Each step has 1 of possible 3 states - # 0 - NO-OP - not executed - # 1 - STARTED - Started - # 2 - SUCCESSFUL - Successfully Completed - # 3 - FAILED - Completed with Error -CreateNonRootUser=0 -CreateSSHKey=0 -SecureAuthkeysfile=0 -ChangeSourceList=0 -InstallReqSoftwares=0 -ConfigureUFW=0 -ConfigureFail2Ban=0 -ChangeRootPwd=0 -ScheduleUpdate=0 -EnableSSHOnly=0 - -STEP_TEXT=( - "Creating new user" #0 - "Creating SSH Key for new user" #1 - "Securing 'authorized_keys' file" #2 - "Enabling SSH-only login" #3 - "Reset sources.list to defaults" #4 - "Installing required softwares" #5 - "Configure UFW" #6 - "Configure Fail2Ban" #7 - "Changing root password" #8 - "Scheduling daily update download" #9 -) - -function set_exit_code() { - if [[ $OP_CODE -eq 0 ]] && [[ $1 -gt 0 ]]; then - exit_code=$1 + return 1 fi } -function reset_exit_code() { - exit_code=0 +revert_create_user() { + # Check if the user exists before attempting to remove + if id "$USERNAME" >/dev/null 2>&1; then + console_log "Info" "Attempting to remove user $USERNAME" + + # Remove user and its home directory + if userdel -r "$USERNAME" >/dev/null 2>&1; then + console_log "Success" "User $USERNAME and home directory removed successfully" + file_log "User $USERNAME and home directory removed successfully" + return 0 + else + console_log "Error" "Failed to remove user $USERNAME" + file_log "Failed to remove user $USERNAME" + return 1 + fi + else + console_log "Info" "No user $USERNAME found to remove" + file_log "No user $USERNAME found to remove" + return 0 + fi } -function update_step_status() { - local event - event=$(get_step_var_from_stepname "$1") - eval "$event"="$2" -} - -function get_step_status() { - step_variable_name=$(get_step_var_from_stepname "$1") - echo "${!step_variable_name}" -} - -function get_step_var_from_stepname() { - case $1 in - "${STEP_TEXT[0]}") - echo "CreateNonRootUser" - ;; - "${STEP_TEXT[1]}") - echo "CreateSSHKey" - ;; - "${STEP_TEXT[2]}") - echo "SecureAuthkeysfile" - ;; - "${STEP_TEXT[3]}") - echo "EnableSSHOnly" - ;; - "${STEP_TEXT[4]}") - echo "ChangeSourceList" - ;; - "${STEP_TEXT[5]}") - echo "InstallReqSoftwares" - ;; - "${STEP_TEXT[6]}") - echo "ConfigureUFW" - ;; - "${STEP_TEXT[7]}") - echo "ConfigureFail2Ban" - ;; - "${STEP_TEXT[8]}") - echo "ChangeRootPwd" - ;; - "${STEP_TEXT[9]}") - echo "ScheduleUpdate" - ;; - *) - false - ;; +console_log() { + # $1: Log level + # $2: Log message + case "$1" in + Success) printf "[ \033[0;32m OK \033[0m ] %s\n" "$2" ;; + Error) printf "[ \033[0;31mFAIL\033[0m ] %s\n" "$2" ;; + Info) printf "[ \033[0;34mINFO\033[0m ] %s\n" "$2" ;; + *) printf "[ ] %s\n" "$2" ;; esac } -function service_action_and_chk_error() { - local servicename=$1 - local serviceaction=$2 - local servicemsg +file_log() { + # $1: Log message + timestamp=$(date '+%Y-%m-%d %H:%M:%S') - servicemsg=$(service "$servicename" "$serviceaction" 2>&1) - file_log "$servicemsg" - return $(echo "$servicemsg" | grep -c 'ERROR') + # Write to logfile with timestamp and log level + printf "%s: %s\n" "$timestamp" "$1" >>"$LOGFILE_NAME" } -function recap() { - if [[ $CreateNonRootUser -eq 2 ]] && - [[ $CreateSSHKey -eq 2 ]] && - [[ $SecureAuthkeysfile -eq 2 ]] && - [[ $InstallReqSoftwares -eq 2 ]] && - [[ $ChangeSourceList -le 2 ]] && # Since 0 (NO-OP) is still success - [[ $ConfigureUFW -le 2 ]] && # Since 0 (NO-OP) is still success - [[ $ConfigureFail2Ban -le 2 ]] && # Since 0 (NO-OP) is still success - [[ $ScheduleUpdate -le 2 ]] && # Since 0 (NO-OP) is still success - [[ $ChangeRootPwd -le 2 ]] && # Since 0 (NO-OP) is still success - [[ $EnableSSHOnly -eq 2 ]]; then - echo - line_fill "$CHORIZONTAL" "$CLINESIZE" - center_reg_text "ALL OPERATIONS COMPLETED SUCCESSFULLY" - fi - - if [[ $ChangeSourceList -eq 3 ]] || - [[ $InstallReqSoftwares -eq 3 ]] || - [[ $ConfigureUFW -eq 3 ]] || - [[ $ConfigureFail2Ban -eq 3 ]] - [[ $ScheduleUpdate -eq 3 ]] && - [[ $ChangeRootPwd -eq 3 ]]; then - center_err_text "Some operations failed..." - center_err_text "System would function with reduced security" - center_err_text "Please check $LOGFILE file for details" - echo - fi - - #Recap - file_log "" - file_log "" - file_log "" - file_log "" - - if [[ $HIDE_CREDENTIALS == "n" ]]; then - line_fill "$CHORIZONTAL" "$CLINESIZE" - fi - log_ops_finish "User Name" "$CreateNonRootUser" "$NORM_USER_NAME" - log_ops_finish "User's Password" "$CreateNonRootUser" "$USER_PASS" - log_ops_finish "SSH Private Key File" "$CreateSSHKey" "$SSH_DIR"/"$NORM_USER_NAME".pem - log_ops_finish "SSH Public Key File" "$CreateSSHKey" "$SSH_DIR"/"$NORM_USER_NAME".pem.pub - log_ops_finish "SSH Key Passphrase" "$CreateSSHKey" "$KEY_PASS" - if [[ "$RESET_ROOT_PWD" == "y" && "$USER_CREATION_ALONE" == "n" ]]; then - log_ops_finish "New root Password" "$ChangeRootPwd" "$PASS_ROOT" - fi - if [[ $HIDE_CREDENTIALS == "n" ]]; then - line_fill "$CHORIZONTAL" "$CLINESIZE" - fi - - log_ops_finish_file_contents "SSH Private Key" "$SSH_DIR"/"$NORM_USER_NAME".pem - log_ops_finish_file_contents "SSH Public Key" "$SSH_DIR"/"$NORM_USER_NAME".pem.pub - - line_fill "$CHORIZONTAL" "$CLINESIZE" - center_reg_text "!!! DO NOT LOG OUT JUST YET !!!" - center_reg_text "Use another window to test out the above credentials" - center_reg_text "If you face issue logging in, check the log file to see what went wrong" - center_reg_text "Log file at ${LOGFILE}" - - line_fill "$CHORIZONTAL" "$CLINESIZE" - echo - - if [[ $HIDE_CREDENTIALS == "y" ]]; then - center_reg_text "Use the following command to see all credentials" - center_reg_text "tail -n 20 ${LOGFILE}" - fi - - file_log "Total execution time in seconds - ${SECONDS}" - center_reg_text "Total execution time - ${SECONDS}s" - - exit +create_logfile() { + touch "$LOGFILE_NAME" } -function setup_step_start() { - reset_exit_code - update_step_status "$1" 1 - log_step_status "$1" -} +main() { + create_logfile + parse_args "$@" -function setup_step_end() { - # If it was a no op - Log and return - # Since we set step status to be 1 in setup_step_start(), - # it can become 0 only if we explicitly do the following INSIDE the step - # update_step_status "$1" 0 - if [[ $(get_step_status "$1") -eq 0 ]]; then - log_step_status "$1" "NO-OP" - return - fi + console_log "Info" "Starting user creation process" - if [[ $exit_code -eq 0 ]]; then - update_step_status "$1" 2 - log_step_status "$1" "SUCCESSFUL" + if create_user; then + console_log "Success" "User creation completed" + file_log "User creation process completed successfully" else - file_log "Error code - ${exit_code}" - update_step_status "$1" 3 - log_step_status "$1" "FAILED" + console_log "Error" "User creation failed" + file_log "User creation process failed" + exit 1 fi + + console_log "Success" "Installation success" + console_log "Error" "Could not copy stuff" + console_log "Info" "Took 3 minutes to complete script" + + file_log "Installation success" + file_log "Could not copy stuff" + file_log "Took 3 minutes to complete script" } - -############################################################## -# Step 1 - Create non-root user -############################################################## - -setup_step_start "${STEP_TEXT[0]}" -{ - if [[ $AUTO_GEN_USERNAME == 'y' ]]; then - NORM_USER_NAME="$(< /dev/urandom tr -cd 'a-z' | head -c 6)""$(< /dev/urandom tr -cd '0-9' | head -c 2)" || exit 1 - file_log "Generated user name ${NORM_USER_NAME}" - fi - - # Generate a 15 character random password - USER_PASS="$(< /dev/urandom tr -cd "[:alnum:]" | head -c 15)" || exit 1 - file_log "Generated user password - ${USER_PASS}" - - # Create the user and assign the above password - file_log "Creating user" - echo -e "${USER_PASS}\\n${USER_PASS}" | adduser "$NORM_USER_NAME" -q --gecos "First Last,RoomNumber,WorkPhone,HomePhone" - set_exit_code $? - - # Give root privilages to the above user - file_log "Assigning user sudo privileges" - usermod -aG sudo "$NORM_USER_NAME" - set_exit_code $? -} 2>> "$LOGFILE" >&2 - -setup_step_end "${STEP_TEXT[0]}" -if [[ $exit_code -gt 0 ]]; then - revert_everything_and_exit "${STEP_TEXT[0]}" -fi - - -############################################################## -# Step 2 - Create SSH Key for the above new user -############################################################## - -setup_step_start "${STEP_TEXT[1]}" -{ - SSH_DIR=/home/"$NORM_USER_NAME"/.ssh - file_log "Creating SSH directory - $SSH_DIR" - mkdir "$SSH_DIR" - set_exit_code $? - - # Generate a 15 character random password for key - KEY_PASS="$(< /dev/urandom tr -cd "[:alnum:]" | head -c 15)" - file_log "Generated SSH Key Passphrase - ${KEY_PASS}" - set_exit_code $? - - # Create a OpenSSH-compliant ed25519-type key - file_log "Generating SSH Key File - $SSH_DIR/$NORM_USER_NAME.pem" - ssh-keygen -a 1000 -o -t ed25519 -N "$KEY_PASS" -C "$NORM_USER_NAME" -f "$SSH_DIR"/"$NORM_USER_NAME".pem -q - set_exit_code $? - - # Copy the generated public file to authorized_keys - cat "$SSH_DIR"/"$NORM_USER_NAME".pem.pub >> "$SSH_DIR"/authorized_keys - set_exit_code $? -} 2>> "$LOGFILE" >&2 - -setup_step_end "${STEP_TEXT[1]}" -if [[ $exit_code -gt 0 ]]; then - file_log "Creating SSH Key for new user failed." - revert_everything_and_exit "${STEP_TEXT[1]}" -fi - - -############################################################## -# Step 3 - Secure authorized_keys file -############################################################## - -setup_step_start "${STEP_TEXT[2]}" -{ - # Set appropriate permissions for ".ssh" dir and "authorized_key" file - file_log "Setting appropriate permissions for $SSH_DIR dir and $SSH_DIR/authorized_keys file" - chown -R "$NORM_USER_NAME" "$SSH_DIR" && \ - chgrp -R "$NORM_USER_NAME" "$SSH_DIR" && \ - chmod 700 "$SSH_DIR" && \ - chmod 400 "$SSH_DIR"/authorized_keys && \ - chattr +i "$SSH_DIR"/authorized_keys - set_exit_code $? - - # Restrict access to the generated SSH Key files as well - shopt -s nullglob - KEY_FILES=("$SSH_DIR"/"$NORM_USER_NAME".pem*) - for key in "${KEY_FILES[@]}"; do - file_log "Restricting access (chmod 400 and chattr +i) to ${key} file" - chmod 400 "$key" && \ - chattr +i "$key" - set_exit_code $? - done -} 2>> "$LOGFILE" >&2 - -setup_step_end "${STEP_TEXT[2]}" -if [[ $exit_code -gt 0 ]]; then - file_log "Setting restrictive permissions for '~/.ssh/' directory failed" - file_log "Please do 'ls -lAh ~/.ssh/' and check manually to see what went wrong." - revert_everything_and_exit "${STEP_TEXT[2]}" -fi - -if [[ "$USER_CREATION_ALONE" == "y" ]]; then - recap -fi - - -############################################################## -# Step 4 - Change default source-list -############################################################## - -if [[ $DEFAULT_SOURCE_LIST = "y" ]]; then - # Low priority - But what to do if it fails??? - setup_step_start "${STEP_TEXT[4]}" - { - file_log "Backing up /etc/apt/sources.list file to /etc/apt/sources.list${BACKUP_EXTENSION}" - cp /etc/apt/sources.list /etc/apt/sources.list"${BACKUP_EXTENSION}" - set_exit_code $? - - file_log "Commenting out everthing in /etc/apt/sources.list" - sed -i "1,$(wc -l < /etc/apt/sources.list) s/^/#/" /etc/apt/sources.list - set_exit_code $? - - if [[ $OS = "debian" ]]; then - - file_log "Adding default CDN sources to /etc/apt/sources.list" - -# Default sources list for debian -cat <> /etc/apt/sources.list - -deb http://deb.debian.org/debian ${DEB_VER_STR} main contrib non-free -deb-src http://deb.debian.org/debian ${DEB_VER_STR} main contrib non-free - -## Major bug fix updates produced after the final release of the -## distribution. -deb http://security.debian.org ${DEB_VER_STR}/updates main contrib non-free -deb-src http://security.debian.org ${DEB_VER_STR}/updates main contrib non-free - -deb http://deb.debian.org/debian ${DEB_VER_STR}-updates main contrib non-free -deb-src http://deb.debian.org/debian ${DEB_VER_STR}-updates main contrib non-free - -deb http://deb.debian.org/debian ${DEB_VER_STR}-backports main contrib non-free -deb-src http://deb.debian.org/debian ${DEB_VER_STR}-backports main contrib non-free -DEBIAN - set_exit_code $? - - elif [[ $OS = "ubuntu" ]]; then - -cat <> /etc/apt/sources.list - -deb http://archive.ubuntu.com/ubuntu/ ${UBT_VER_STR} main restricted -deb-src http://archive.ubuntu.com/ubuntu/ ${UBT_VER_STR} main restricted - -deb http://archive.ubuntu.com/ubuntu/ ${UBT_VER_STR}-updates main restricted -deb-src http://archive.ubuntu.com/ubuntu/ ${UBT_VER_STR}-updates main restricted - -deb http://archive.ubuntu.com/ubuntu/ ${UBT_VER_STR} universe multiverse -deb-src http://archive.ubuntu.com/ubuntu/ ${UBT_VER_STR} universe multiverse -deb http://archive.ubuntu.com/ubuntu/ ${UBT_VER_STR}-updates universe multiverse -deb-src http://archive.ubuntu.com/ubuntu/ ${UBT_VER_STR}-updates universe multiverse - -deb http://archive.ubuntu.com/ubuntu/ ${UBT_VER_STR}-backports main restricted universe multiverse -deb-src http://archive.ubuntu.com/ubuntu/ ${UBT_VER_STR}-backports main restricted universe multiverse - -deb http://security.ubuntu.com/ubuntu ${UBT_VER_STR}-security main restricted -deb-src http://security.ubuntu.com/ubuntu ${UBT_VER_STR}-security main restricted -deb http://security.ubuntu.com/ubuntu ${UBT_VER_STR}-security universe -deb-src http://security.ubuntu.com/ubuntu ${UBT_VER_STR}-security universe -deb http://security.ubuntu.com/ubuntu ${UBT_VER_STR}-security multiverse -deb-src http://security.ubuntu.com/ubuntu ${UBT_VER_STR}-security multiverse -UBUNTU - set_exit_code $? - fi - - # Find any additional sources listed by the provider and comment them out - SOURCE_FILES=(/etc/apt/source*/*.list) - if [[ ${#SOURCE_FILES[@]} -gt 0 ]]; then - for file in "${SOURCE_FILES[@]}"; - do - file_log "Backing up ${file} file to ${file}${BACKUP_EXTENSION}" - cp "$file" "$file""${BACKUP_EXTENSION}" - set_exit_code $? - - file_log "Commenting out the ${file}" - sed -i "1,$(wc -l < "$file") s/^/#/" "$file" - set_exit_code $? - done - fi - } 2>> "$LOGFILE" >&2 - - setup_step_end "${STEP_TEXT[4]}" - if [[ $exit_code -gt 0 ]]; then - revert_source_list_changes - fi -fi - - -############################################################## -# Step 5 - Install required softwares -############################################################## - -setup_step_start "${STEP_TEXT[5]}" -{ - file_log "Cleaning apt cache" - apt-get -y clean && apt-get -y autoclean && apt-get -y autoremove - - file_log "Updating apt-get" - apt-get update - - file_log "Downloading apt updates" - export DEBIAN_FRONTEND=noninteractive ; apt-get upgrade -y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" - apt-get install -y sudo curl screen ufw fail2ban - set_exit_code $? - - file_log "To install updates, run - sudo apt-get dist-upgrade" -} 2>> "$LOGFILE" >&2 - -setup_step_end "${STEP_TEXT[5]}" -if [[ $exit_code -gt 0 ]]; then - revert_software_installs -fi - -############################################################## -# Step 6 - Configure UFW -############################################################## - -# Check if UFW is installed -ufw status 2>> /dev/null >&2 - -# Proceed only when UFW is installed -if [[ $? -eq 0 ]]; then - setup_step_start "${STEP_TEXT[6]}" - { - file_log "Setting ufw for ssh, http, https" - ufw allow ssh && ufw allow http && ufw allow https - set_exit_code $? - - file_log "Enabling ufw" - echo "y" | ufw enable - set_exit_code $? - } 2>> "$LOGFILE" >&2 -else - update_step_status "$1" 0 - file_log "Skipping UFW config as it does not seem to be installed - check log to know more" -fi - -setup_step_end "${STEP_TEXT[6]}" -if [[ $exit_code -gt 0 ]]; then - revert_config_UFW -fi - - -############################################################## -# Step 7 - Configure Fail2Ban -############################################################## - -# Proceed only when Fail2ban is installed -if [[ $(dpkg -l | grep -c fail2ban) -gt 0 ]]; then - setup_step_start "${STEP_TEXT[7]}" - { - if [[ -f /etc/fail2ban/jail.local ]]; then - file_log "Backing up /etc/fail2ban/jail.local to /etc/fail2ban/jail.local${BACKUP_EXTENSION}" - cp /etc/fail2ban/jail.local /etc/fail2ban/jail.local"$BACKUP_EXTENSION" - set_exit_code $? - else - file_log "Copying /etc/fail2ban/jail.conf to /etc/fail2ban/jail.local" - cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local - set_exit_code $? - - file_log "Backing up /etc/fail2ban/jail.conf to /etc/fail2ban/jail.conf${BACKUP_EXTENSION}" - cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.conf"$BACKUP_EXTENSION" - set_exit_code $? - fi - - # Do not do anything if copying jail.conf to jail.local failed - if [[ -f /etc/fail2ban/jail.local ]]; then - file_log "Determining your physical IP from https://ipinfo.io/ip" - pub_ip=$(curl https://ipinfo.io/ip 2>> /dev/null) - - # Start search from the line that contains "[DEFAULT]" - end search before the line that contains "# JAILS" - file_log "/etc/fail2ban/jail.local - Setting bantime = 18000" - sed -ri "/^\[DEFAULT\]$/,/^# JAILS$/ s/^bantime[[:blank:]]*= .*/bantime = 18000/" /etc/fail2ban/jail.local - set_exit_code $? - - file_log "/etc/fail2ban/jail.local - Setting backend = polling" - sed -ri "/^\[DEFAULT\]$/,/^# JAILS$/ s/^backend[[:blank:]]*=.*/backend = polling/" /etc/fail2ban/jail.local - set_exit_code $? - - file_log "/etc/fail2ban/jail.local - Setting ignoreip = 127.0.0.1/8 ::1 ${pub_ip}" - sed -ri "/^\[DEFAULT\]$/,/^# JAILS$/ s/^ignoreip[[:blank:]]*=.*/ignoreip = 127.0.0.1\/8 ::1 ${pub_ip}/" /etc/fail2ban/jail.local - set_exit_code $? - - # TODO - Exception handle - # - No [DEFAULT] section present - # - no "bantime" or "backend" or "ignoreip" - options present - # But that is not very important - cause fail2ban defaults are sane anyways - fi - - if [[ -f /etc/fail2ban/jail.d/defaults-debian.conf ]]; then - file_log "Backing up /etc/fail2ban/jail.d/defaults-debian.conf to /etc/fail2ban/jail.d/defaults-debian.conf${BACKUP_EXTENSION}" - cp /etc/fail2ban/jail.d/defaults-debian.conf /etc/fail2ban/jail.d/defaults-debian.conf"$BACKUP_EXTENSION" - set_exit_code $? - fi - - file_log "Enabling jails in /etc/fail2ban/jail.d/defaults-debian.conf" -cat < /etc/fail2ban/jail.d/defaults-debian.conf -[sshd] -enabled = true -maxretry = 3 -bantime = 2592000 - -[sshd-ddos] -enabled = true -maxretry = 5 -bantime = 2592000 - -[recidive] -enabled = true -bantime = 31536000 ; 1 year -findtime = 86400 ; 1 days -maxretry = 10 -FAIL2BAN - set_exit_code $? - - set_exit_code $(service_action_and_chk_error "fail2ban" "start") - } 2>> "$LOGFILE" >&2 -else - update_step_status "$1" 0 - file_log "Skipping Fail2Ban config as it does not seem to be installed - check log to know more" -fi - -setup_step_end "${STEP_TEXT[7]}" -if [[ $exit_code -gt 0 ]]; then - revert_config_fail2ban -fi - - -############################################################## -# Step 8 - Schedule cron for daily system update -############################################################## - -setup_step_start "${STEP_TEXT[9]}" -{ - dailycron_filename=/etc/cron.daily/linux_init_harden_apt_update.sh - - # Check if we created a schedule already - if [[ -f $dailycron_filename ]] ; then - file_log "$dailycron_filename file already exists. Skipping this step..." - update_step_status "${STEP_TEXT[9]}" 0 - else - # If not created already - create one into the file - file_log "Adding our schedule to the script file ${dailycron_filename}" - echo "#!/bin/sh" >> $dailycron_filename - echo 'apt-get update && apt-get -y -d upgrade' >> $dailycron_filename - set_exit_code $? - - file_log "Granting execute permission on ${dailycron_filename} file" - chmod +x $dailycron_filename - set_exit_code $? - fi -} 2>> "$LOGFILE" >&2 - -setup_step_end "${STEP_TEXT[9]}" -if [[ $exit_code -gt 0 ]]; then - revert_schedule_updates -fi - - -############################################################## -# Step 9 - Change root's password -############################################################## - -if [[ $RESET_ROOT_PWD == 'y' ]]; then - setup_step_start "${STEP_TEXT[8]}" - { - # Generate a 15 character random password - file_log "Generating roots new password..." - PASS_ROOT="$(< /dev/urandom tr -cd "[:alnum:]" | head -c 15)" - set_exit_code $? - - file_log "Generated root Password - ${PASS_ROOT}" - - # Change root's password - file_log "Setting the new root password" - echo -e "${PASS_ROOT}\\n${PASS_ROOT}" | passwd root - set_exit_code $? - } 2>> "$LOGFILE" >&2 - - setup_step_end "${STEP_TEXT[8]}" - if [[ $exit_code -gt 0 ]]; then - revert_root_pass_change - fi -fi - - -############################################################## -# Step 10 - Enable SSH-only login -############################################################## - -# TODO - Make this cleaner -function config_search_regex(){ - local search_key=$1 - declare -i isCommented=$2 - local value=$3 - - if [[ "$isCommented" -eq 1 ]] && [[ ! "$value" ]]; then - # Search Regex for an uncommented (active) field - echo '(^ *)'"$search_key"'( *).*([[:word:]]+)( *)$' - elif [[ "$isCommented" -eq 2 ]] && [[ ! "$value" ]]; then - # Search Regex for a commented out field - echo '(^ *)#.*'"$search_key"'( *).*([[:word:]]+)( *)$' - - elif [[ "$isCommented" -eq 1 ]] && [[ "$value" ]]; then - # Search Regex for an active field with specified value - echo '(^ *)'"$search_key"'( *)('"$value"')( *)$' - elif [[ "$isCommented" -eq 2 ]] && [[ "$value" ]]; then - # Search Regex for an commented (inactive) field with specified value - echo '(^ *)#.*'"$search_key"'( *)('"$value"')( *)$' - - else - exit 1 - fi -} - -function set_config_key(){ - local file_location=$1 - local key=$2 - local value=$3 - - ACTIVE_KEYS_REGEX=$(config_search_regex "$key" "1") - ACTIVE_CORRECT_KEYS_REGEX=$(config_search_regex "$key" "1" "$value") - INACTIVE_KEYS_REGEX=$(config_search_regex "$key" "2") - - # If no keys present - insert the correct configuration to the end of the file - if [[ $(grep -Pnc "$INACTIVE_KEYS_REGEX" "$file_location") -eq 0 ]] && [[ $(grep -Pnc "$ACTIVE_KEYS_REGEX" "$file_location") -eq 0 ]]; - then - echo "$key" "$value" >> "$file_location" - fi - - # If Config file already has correct configuration - # Keep only the LAST correct one and comment out the rest - if [[ $(grep -Pnc "$ACTIVE_KEYS_REGEX" "$file_location") -gt 0 ]]; - then - # Last correct active entry's line number - LAST_CORRECT_LINE=$(grep -Pn "$ACTIVE_CORRECT_KEYS_REGEX" "$file_location" | tail -1 | cut -d: -f 1) - - # Loop through each of the active lines - grep -Pn "$ACTIVE_KEYS_REGEX" "$file_location" | while read -r i; - do - # Get the line number - LINE_NUMBER=$(echo "$i" | cut -d: -f 1 ) - - # If this is the last correct entry - break - if [[ $LAST_CORRECT_LINE -ne 0 ]] && [[ $LINE_NUMBER == "$LAST_CORRECT_LINE" ]]; then - break - fi - - # Comment out the line - sed -i "$LINE_NUMBER"'s/.*/#&/' "$file_location" - done - fi - - # If Config file has commented configuration and NO active configuration - # Append the appropriate configuration below the LAST commented configuration - if [[ $(grep -Pnc "$INACTIVE_KEYS_REGEX" "$file_location") -gt 0 ]] && [[ $(grep -Pnc "$ACTIVE_KEYS_REGEX" "$file_location") -eq 0 ]]; - then - # Get the line number of - last commented configuration - LINE_NUMBER=$(grep -Pn "$INACTIVE_KEYS_REGEX" "$file_location" | tail -1 | cut -d: -f 1) - - (( LINE_NUMBER++ )) - - # Insert the correct setting below the last commented configuration - sed -i "$LINE_NUMBER"'i'"$key"' '"$value" "$file_location" - fi -} - -setup_step_start "${STEP_TEXT[3]}" -{ - # Backup the sshd_config file - file_log "Backing up /etc/ssh/sshd_config file to /etc/ssh/sshd_config$BACKUP_EXTENSION" - cp /etc/ssh/sshd_config /etc/ssh/sshd_config"$BACKUP_EXTENSION" - set_exit_code $? - - # Remove root login - file_log "Removing root login -> PermitRootLogin no" - set_config_key "/etc/ssh/sshd_config" "PermitRootLogin" "no" - set_exit_code $? - - # Disable password login - file_log "Disabling password login -> PasswordAuthentication no" - set_config_key "/etc/ssh/sshd_config" "PasswordAuthentication" "no" - set_exit_code $? - - # Set SSH Authorization-Keys path - file_log "Setting SSH Authorization-Keys path -> AuthorizedKeysFile '%h\/\.ssh\/authorized_keys'" - set_config_key "/etc/ssh/sshd_config" "AuthorizedKeysFile" '\.ssh\/authorized_keys %h\/\.ssh\/authorized_keys' - set_exit_code $? - - file_log "Restarting ssh service..." - { - set_exit_code $(service_action_and_chk_error "sshd" "restart") - if [[ $exit_code -eq 0 ]]; then - false - fi - } || { - # Because Ubuntu 14.04 does not have sshd - set_exit_code $(service_action_and_chk_error "ssh" "restart") - } -} 2>> "$LOGFILE" >&2 - -setup_step_end "${STEP_TEXT[3]}" -if [[ $exit_code -gt 0 ]]; then - file_log "Enabling SSH-only login failed." - revert_everything_and_exit "${STEP_TEXT[3]}" -fi - - -############################################################## -# Recap -############################################################## - -recap +main "$@"