#!/etc/bin/env bash SCRIPT_NAME=linux_init_harden SCRIPT_VERSION=1.0 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" # 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 } # Check supported OSes if [ -f /etc/os-release ]; then # freedesktop.org and systemd . /etc/os-release OS=$ID VER=$VERSION_ID else # Fall back to uname, e.g. "Linux ", also works for BSD, etc. OS=$(uname -s) VER=$(uname -r) fi case "$OS" in debian) if [[ "$VER" -eq 8 ]]; then DEB_VER_STR="jessie" elif [[ "$VER" -eq 9 ]]; then DEB_VER_STR="stretch" elif [[ "$VER" -eq 10 ]]; then DEB_VER_STR="buster" else printf "This script only supports Debian 8 and Debian 9, and Debian 10\\n" printf "\\tUbuntu 14.04, Ubuntu 16.04, Ubuntu 18.04, Ubuntu 18.10, and Ubuntu 20.04\\n" printf "Your OS is NOT supported.\\n" exit 1 fi ;; ubuntu) if [[ "$VER" = "14.04" ]]; then UBT_VER_STR="trusty" elif [[ "$VER" = "16.04" ]]; then UBT_VER_STR="xenial" elif [[ "$VER" = "18.04" ]]; then UBT_VER_STR="bionic" elif [[ "$VER" = "18.10" ]]; then UBT_VER_STR="cosmic" elif [[ "$VER" = "20.04" ]]; then UBT_VER_STR="focal" else printf "This script only supports Debian 8, Debian 9, and Debian 10\\n" printf "\\tUbuntu 14.04, Ubuntu 16.04, Ubuntu 18.04, Ubuntu 18.10, and Ubuntu 20.04\\n" printf "Your OS is NOT supported.\\n" exit 1 fi ;; *) printf "This script only supports Debian 8 and Debian 9\\n" printf "\\tUbuntu 14.04, Ubuntu 16.04, Ubuntu 18.04, Ubuntu 18.10\\n" printf "Your OS is NOT supported.\\n" 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 else AUTO_GEN_USERNAME="n" NORM_USER_NAME="$2" 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 <> "$LOGFILE" } function log_step_status() { local EVENT=$1 local RESULT=$2 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..." 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 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 fi } function reset_exit_code() { exit_code=0 } 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 ;; esac } function service_action_and_chk_error() { local servicename=$1 local serviceaction=$2 local servicemsg servicemsg=$(service "$servicename" "$serviceaction" 2>&1) file_log "$servicemsg" return $(echo "$servicemsg" | grep -c 'ERROR') } 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 } function setup_step_start() { reset_exit_code update_step_status "$1" 1 log_step_status "$1" } 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 if [[ $exit_code -eq 0 ]]; then update_step_status "$1" 2 log_step_status "$1" "SUCCESSFUL" else file_log "Error code - ${exit_code}" update_step_status "$1" 3 log_step_status "$1" "FAILED" fi } ############################################################## # 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