From c533bb8937cea521e80d29f651cd4dcd200e7c6c Mon Sep 17 00:00:00 2001 From: Pratik Date: Wed, 16 Jan 2019 21:51:40 +0530 Subject: [PATCH] Updates --- init-linux-harden.sh | 463 ++++++++++++++++++++++++++++--------------- 1 file changed, 304 insertions(+), 159 deletions(-) diff --git a/init-linux-harden.sh b/init-linux-harden.sh index 080897b..a5eb79b 100644 --- a/init-linux-harden.sh +++ b/init-linux-harden.sh @@ -14,7 +14,7 @@ # Ask him to report back if he can login using the new user -with the ssh-private key # - tell him to talk to the server provider's support to get help regarding SSH-only access # What to do if making .bkp file fails? - # Add timestamp to all backup files filename.071218_171731_bak + # Add timestamp` to all backup files filename.071218_171731_bak #Test # 1 - Deb 9.x # 2 - Deb 8.x @@ -28,6 +28,13 @@ SCRIPT_NAME=server_harden SCRIPT_VERSION=0.2 LOGFILE=/tmp/"$SCRIPT_NAME"_v"$SCRIPT_VERSION".log +BACKUP_EXTENSION='.'$(date '+%d%m%Y%H%M%S')"_bak" + +# Colors +CSI='\033[' +CEND="${CSI}0m" +CRED="${CSI}1;31m" +CGREEN="${CSI}1;32m" ############################################################## # Basic checks before starting @@ -50,58 +57,85 @@ else exit 1 fi +################################## +# Parse script arguments +################################## -############################################################## -# Gather info -############################################################## +# Script takes arguments as follows +# init-linux-harden -username=pratik --resetroot +# init-linux-harden -u pratik --resetroot -# Change root user's password -# Choose a user name -clear -echo "Do you want to change root password ? (y/n)" -echo "(You might want to do this if you received it as an email from your host.)" -while [[ $RESET_ROOT_PWD != "y" && $RESET_ROOT_PWD != "n" ]]; do - read -rp "Select an option (y/n): " RESET_ROOT_PWD - RESET_ROOT_PWD=$(echo "$RESET_ROOT_PWD" | head -c 1) -done +function usage() { + if [ -n "$1" ]; then + echo "" + echo -e "${CRED}$1${CEND}\n" + fi -echo "Allow this script to randomly generate a username for you ? (y/n)" - while [[ $AUTO_GEN_USERNAME != "y" && $AUTO_GEN_USERNAME != "n" ]]; do - read -rp "Select an option (y/n): " AUTO_GEN_USERNAME -done + echo "Usage: $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 " -d, --defaultsourcelist Updates /etc/apt/sources.list to download software from debian.org." + echo " NOTE - If you fail to update system after using it, you need to manually reset it. This script keeps a backup in the same folder." -if [[ $AUTO_GEN_USERNAME == 'n' ]]; then - while [[ ! "$NORM_USER_NAME" ]]; do - printf "Please provide a user name - \\n" - printf "%2s - [a-zA-Z0-9] [-] [_] are allowed\\n%2s - NO special characters.\\n%2s - NO spaces.\\n:" " " " " " " - read -r NORM_USER_NAME - - # If the user exists or invalid characters - ask for a different username - if [[ $(echo "$NORM_USER_NAME" | grep -Pnc '^[a-zA-Z0-9_-]+$') -eq 0 ]] || - [[ $(getent passwd "$NORM_USER_NAME" | wc -l) -gt 0 ]]; then - NORM_USER_NAME="" - printf "%2s !!! User name already exists or \\n%2s !!! Invalid characters in the User name.\\n" " " " " - continue - fi - done -else echo "" -fi + echo "Example: $0 --username myuseraccount --resetrootpwd" + printf "\\nBelow restrictions apply to username this script accepts - \\n" + printf "%2s - [a-zA-Z0-9] [-] [_] are allowed\\n%2s - NO special characters.\\n%2s - NO spaces.\\n" " " " " " " +} + +# defaults +AUTO_GEN_USERNAME="y" +RESET_ROOT_PWD="n" +DEFAULT_SOURCE_LIST="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 + ;; + -r|--resetrootpwd) + RESET_ROOT_PWD="y" + shift + ;; + -d|--defaultsourcelist) + DEFAULT_SOURCE_LIST="y" + shift + ;; + -h|--help) + usage + shift + ;; + *) + usage "Unknown parameter passed: $1" "h" + exit 1 + shift + shift + ;; + esac +done ############################################################## # Log ############################################################## -CSI='\033[' -CEND="${CSI}0m" -CRED="${CSI}1;31m" -CGREEN="${CSI}1;32m" CVERTICAL="|" CHORIZONTAL="_" -# Reset privilous log file -printf "" > "$LOGFILE" function horizontal_fill() { local char=$1 @@ -131,6 +165,98 @@ function recap (){ line_fill "$CVERTICAL" 1 } +function revert_changes(){ + if [[ $1 = "Creating new user" ]]; then + revert_create_user + elif [[ $1 = "Creating SSH Key for new user" ]]; then + revert_create_ssh_key + elif [[ $1 = "Adding SSH Key to 'authorized_keys' file" ]]; then + revert_add_to_authorized_key + elif [[ $1 = "Securing 'authorized_keys' file" ]]; then + revert_secure_authorized_key + elif [[ $1 = "Enabling SSH-only login" ]]; then + revert_ssh_only_login + elif [[ $1 = "Changing urls in sources.list to defaults" ]]; then + # This can be reverted back individually + revert_source_list_changes + elif [[ $1 = "Installing required softwares" ]]; then + # This can be reverted back individually + return 7 + elif [[ $1 = "Changing root password" ]]; then + # CANNOT be reverted back + # Just use your old password + revert_root_pass_change + fi +} + +function revert_create_user(){ + # Remove user and + if [[ $(getent passwd "$NORM_USER_NAME" | wc -l) -gt 0 ]]; then + deluser --remove-home "$NORM_USER_NAME" + fi +} + +function revert_create_ssh_key(){ + + revert_create_user + + KEY_FILE_BKPS=("$SSH_DIR"/"$NORM_USER_NAME".pem*"$BACKUP_EXTENSION") + + if [[ ${#KEY_FILE_BKPS[@]} -gt 0 ]]; then + unalias cp + for key in "${KEY_FILE_BKPS[@]}"; do + cp -rf "$key" "${key//$BACKUP_EXTENSION/}" || exit 1 + done + fi +} + +function revert_add_to_authorized_key(){ + revert_create_ssh_key + + if [[ -f "$SSH_DIR"/authorized_keys"$BACKUP_EXTENSION" ]]; then + unalias cp + chattr -i "$SSH_DIR"/authorized_keys + #chmod 700 "$SSH_DIR"/authorized_keys + cp -rf "$SSH_DIR"/authorized_keys"$BACKUP_EXTENSION" "$SSH_DIR"/authorized_keys + chmod 400 "$SSH_DIR"/authorized_keys + chattr +i "$SSH_DIR"/authorized_keys + fi +} + +function revert_secure_authorized_key(){ + revert_add_to_authorized_key +} + +function revert_ssh_only_login(){ + revert_secure_authorized_key + + if [[ -f /etc/ssh/sshd_config"$BACKUP_EXTENSION" ]]; then + unalias cp + cp -rf /etc/ssh/sshd_config"$BACKUP_EXTENSION" /etc/ssh/sshd_config + fi +} + +function revert_source_list_changes(){ + if [[ -f /etc/apt/sources.list"${BACKUP_EXTENSION}" ]]; then + unalias cp + cp -rf /etc/apt/sources.list"${BACKUP_EXTENSION}" /etc/apt/sources.list + fi + + SOURCE_FILES_BKP=(/etc/apt/source*/*.list"${BACKUP_EXTENSION}") + if [[ ${#SOURCE_FILES_BKP[@]} -gt 0 ]]; then + unalias cp + for file in "${SOURCE_FILES[@]}"; + do + cp -rf "$file" "${file//$BACKUP_EXTENSION/}" || exit 1 + done + fi +} + +function revert_root_pass_change(){ + # If root password changed - show the new root password + true +} + function finally(){ # Check if $what_failed is one of the catastrophic failures # if - Catastrofic failure - Check if any .bkp file exist and revert them to original @@ -146,10 +272,18 @@ function finally(){ recap "User's SSH Public Key Location" "$KEY_PASS" recap "User's SSH Key Passphrase" "$KEY_PASS" line_fill "$CHORIZONTAL" 60 + + # If something failed - try to revert things back + if [[ "$#" -gt 0 ]]; then + # show - something failed - trying to restore required changes + revert_changes "$1" + + # If restoration failed - well you are f**ked + fi } function file_log(){ - printf "%s - %s\\n" "$(date '+%Y-%m-%d %H:%M:%S')" "$1" >> "$LOGFILE" + printf "%s - %s\\n" "$(date '+%d-%b-%Y %H:%M:%S')" "$1" >> "$LOGFILE" } function op_log() { @@ -165,29 +299,15 @@ function op_log() { printf "\r%30s %7s [${CRED}${RESULT}${CEND}]\\n" "$EVENT" " " printf "\\n\\nPlease look at %s\\n\\n" "$LOGFILE" file_log "${EVENT} - ${RESULT}" + finally "$EVENT" else printf "%30s %7s [${CRED}..${CEND}]" "$EVENT" " " file_log "${EVENT} - begin..." fi } - -declare SESSION_TYPE="" -# Check if the user connected through SSH -if [[ -n "$SSH_CLIENT" ]] || [[ -n "$SSH_TTY" ]]; then - SESSION_TYPE=remote/ssh -else - case $(ps -o comm= -p $PPID) in - sshd|*/sshd) - SESSION_TYPE=remote/ssh; - esac -fi - -if [[ $SESSION_TYPE == "remote/ssh" ]]; then - file_log "Connected through SSH session." -else - file_log "Connected using password authentication." -fi +# Reset previous log file +echo "Starting $0 - $(date '+%d-%b-%Y %H:%M:%S')" > "$LOGFILE" ############################################################## @@ -202,13 +322,13 @@ This script performs the following tasks :- 1 - Change your root password (unless you have choosen NOT to) 2 - Create a non-root user (unless provided by you a random username will be created) - 3 - Generate SSH keys on the server and store them at ~/.ssh + 3 - Generate SSH keys on the server and store them at '~/.ssh' 4 - Adds the public key from the above step to - ~/.ssh/authorized_keys file - 5 - Restricts access to the SSH ~/.ssh folder + '~/.ssh/authorized_keys' file + 5 - Restricts access to '~/.ssh' folder 6 - Restricts login method to SSH-only by editing - /etc/ssh/sshd_config file to enable - 7 - Restores the /etc/apt/sources.list + '/etc/ssh/sshd_config' file to enable + 7 - Restores the '/etc/apt/sources.list' (Most server provider alter these to serve software from their CDNs) 8 - Installs "sudo" "curl" "screen" @@ -219,55 +339,30 @@ This script performs the following tasks :- d) SSH Private Key e) SSH Public Key -Before editing any file, script creates a back up of that file -(filename.bak) in the same directory. If script detects any -catastrophic error, then it restores the original files. Script -assumes you are running this on a brand new VPS and that DATALOSS -OR LOSS OF ACCESS TO THE SERVER IS NOT A MAJOR CONCERN. If you do -however lose access to the server - most VPS provider allow to -create a new one easily. +Before editing any file, script creates a back up of that file with +in the same directory. If script detects any catastrophic error, then +it restores the original files. Script assumes you are running this +on a brand new VPS and that DATALOSS OR LOSS OF ACCESS TO THE SERVER +IS NOT A MAJOR CONCERN. If you do however lose access to the server +most VPS provider allow to create a new one easily. +All backup files have extension (${BACKUP_EXTENSION}) A log file can be found at ${LOGFILE} -TO CONTINUE (press any key)... +TO CONTINUE (press enter/return)... TO EXIT (ctrl + c)... INFORM read -r clear -############################################################## -# Change root's password -############################################################## - -if [[ $RESET_ROOT_PWD == 'y' ]]; then - { - op_log "Changing root password" - - # Generate a 15 character random password - PASS_ROOT="$(< /dev/urandom tr -cd "[:alnum:]" | head -c 15)" || exit 1 - - file_log "Generated Root Password - ${PASS_ROOT}" - - # Change root's password - echo -e "${PASS_ROOT}\\n${PASS_ROOT}" | passwd - } 2>> "$LOGFILE" >&2 - - if [[ $? -eq 0 ]]; then - op_log "Changing root password" "SUCCESSFUL" - else - # Low priority - since we are disabling root login anyways - op_log "Changing root password" "FAILED" - fi -fi - ############################################################## -# Create a normal user +# Create non-root user ############################################################## + +op_log "Creating new user" { - op_log "Creating new user" - 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}" @@ -294,37 +389,42 @@ fi ############################################################## -# Create SSH Key for the new user +# Create SSH Key for the above new user ############################################################## -{ - op_log "Creating SSH Key for new user" +op_log "Creating SSH Key for new user" +{ shopt -s nullglob + # TODO - Below would capture bak files as well - filter out the bak files KEY_FILES=("$SSH_DIR"/"$NORM_USER_NAME".pem*) - #TODO - If SSH files already exist - rename them to .timestamp_bkp + # If SSH files already exist - rename them to .timestamp_bkp + if [[ ${#KEY_FILES[@]} -gt 0 ]]; then + for key in "${KEY_FILES[@]}"; do + cp "$key" "$key""$BACKUP_EXTENSION" || exit 1 + done + fi - # Create key file only if it does NOT exist - if [[ ! ${KEY_FILES[0]} ]]; then - SSH_DIR=/home/"$NORM_USER_NAME"/.ssh - mkdir "$SSH_DIR" || exit 1 + SSH_DIR=/home/"$NORM_USER_NAME"/.ssh + mkdir "$SSH_DIR" || exit 1 - # Generate a 15 character random password for key - KEY_PASS="$(< /dev/urandom tr -cd "[:alnum:]" | head -c 15)" || exit 1 - file_log "Generated SSH Key Passphrase - ${KEY_PASS}" + # Generate a 15 character random password for key + KEY_PASS="$(< /dev/urandom tr -cd "[:alnum:]" | head -c 15)" || exit 1 + file_log "Generated SSH Key Passphrase - ${KEY_PASS}" - # Create a OpenSSH-compliant ed25519-type key - ssh-keygen -a 1000 -o -t ed25519 -N "$KEY_PASS" -C "$NORM_USER_NAME" -f "$SSH_DIR"/"$NORM_USER_NAME".pem -q || exit 1 + # Create a OpenSSH-compliant ed25519-type key + ssh-keygen -a 1000 -o -t ed25519 -N "$KEY_PASS" -C "$NORM_USER_NAME" -f "$SSH_DIR"/"$NORM_USER_NAME".pem -q || exit 1 - # See if the files actually got created - KEY_FILES=("$SSH_DIR"/"$NORM_USER_NAME".pem*) - if [[ ${#KEY_FILES[@]} -eq 0 ]]; then - file_log "Unknown error occured." - file_log "Could not create SSH key files." - exit 1 - fi + # TODO - Below would capture bak files as well - filter out the bak files + # See if the files actually got created + KEY_FILES=("$SSH_DIR"/"$NORM_USER_NAME".pem*) + if [[ ${#KEY_FILES[@]} -eq 0 ]]; then + file_log "Unknown error occured." + file_log "Could not create SSH key files." + exit 1 fi } 2>> "$LOGFILE" >&2 + if [[ $? -eq 0 ]]; then op_log "Creating SSH Key for new user" "SUCCESSFUL" else @@ -337,15 +437,22 @@ fi ############################################################## # Add generated key to authorized_keys file ############################################################## -{ - op_log "Adding SSH Key to 'authorized_keys' file" - # Create authorized_keys if it does not exist yet - touch "$SSH_DIR"/authorized_keys +op_log "Adding SSH Key to 'authorized_keys' file" +{ + # If 'authorized_keys' exists create backup + # BACKUP_EXTENSION + if [[ -e "$SSH_DIR"/authorized_keys ]]; then + cp "$SSH_DIR"/authorized_keys "$SSH_DIR"/authorized_keys"$BACKUP_EXTENSION" || exit 1 + else + # Create authorized_keys if it does not exist yet + touch "$SSH_DIR"/authorized_keys || exit 1 + fi # Insert the public key into "authoried_keys" file - cat "${KEY_FILES[1]}" >> "$SSH_DIR"/authorized_keys || exit 1 + cat "$SSH_DIR"/"$NORM_USER_NAME".pem.pub >> "$SSH_DIR"/authorized_keys || exit 1 } 2>> "$LOGFILE" >&2 + if [[ $? -eq 0 ]]; then op_log "Adding SSH Key to 'authorized_keys' file" "SUCCESSFUL" else @@ -358,16 +465,23 @@ fi ############################################################## # Secure authorized_keys file ############################################################## + +op_log "Securing 'authorized_keys' file" { - op_log "Securing 'authorized_keys' file" - # Set appropriate permissions for ".ssh" dir and "authorized_key" 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 + + # Restrict access to the generated SSH Key files as well + for key in "${KEY_FILES[@]}"; do + chmod 400 "$key" && \ + chattr +i "$key" + done } 2>> "$LOGFILE" >&2 + if [[ $? -eq 0 ]]; then op_log "Securing 'authorized_keys' file" "SUCCESSFUL" else @@ -458,11 +572,10 @@ function set_config_key(){ fi } +op_log "Enabling SSH-only login" { - op_log "Enabling SSH-only login" - # Backup the sshd_config file - cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak || exit 1 + cp /etc/ssh/sshd_config /etc/ssh/sshd_config"$BACKUP_EXTENSION" || exit 1 # Remove root login set_config_key "/etc/ssh/sshd_config" "PermitRootLogin" "no" @@ -475,6 +588,7 @@ function set_config_key(){ systemctl restart sshd } 2>> "$LOGFILE" >&2 + if [[ $? -eq 0 ]]; then op_log "Enabling SSH-only login" "SUCCESSFUL" else @@ -488,13 +602,14 @@ fi # Change default source-list ############################################################## -# Low priority - But what to do if it fails??? -op_log "Changing urls in sources.list to defaults" +if [[ $DEFAULT_SOURCE_LIST = "y" ]]; then + # Low priority - But what to do if it fails??? + op_log "Changing urls in sources.list to defaults" -mv /etc/apt/sources.list /etc/apt/sources.list.bak 2>> "$LOGFILE" >&2 -sed -i "1,$(wc -l < /etc/apt/sources.list.bak) s/^/#/" /etc/apt/sources.list.bak 2>> "$LOGFILE" >&2 + cp /etc/apt/sources.list /etc/apt/sources.list"${BACKUP_EXTENSION}" 2>> "$LOGFILE" >&2 + sed -i "1,$(wc -l < /etc/apt/sources.list) s/^/#/" /etc/apt/sources.list 2>> "$LOGFILE" >&2 -# Default sources list for debian + # Default sources list for debian cat < /etc/apt/sources.list || exit 1 deb https://deb.debian.org/debian ${DEB_VER_STR} main deb-src https://deb.debian.org/debian ${DEB_VER_STR} main @@ -511,46 +626,76 @@ deb https://deb.debian.org/debian ${DEB_VER_STR}-backports main deb-src https://deb.debian.org/debian ${DEB_VER_STR}-backports main TAG -# Find any additional sources listed by the provider and comment them out -SOURCE_FILES=(/etc/apt/source*/*.list) 2>> "$LOGFILE" >&2 -if [[ ${#SOURCE_FILES[@]} -gt 0 ]]; then - for file in "${SOURCE_FILES[@]}"; - do - mv "$file" "$file".bak 2>> "$LOGFILE" >&2 - sed -i "1,$(wc -l < "$file") s/^/#/" "$file" 2>> "$LOGFILE" >&2 - done + # Find any additional sources listed by the provider and comment them out + SOURCE_FILES=(/etc/apt/source*/*.list) 2>> "$LOGFILE" >&2 + if [[ ${#SOURCE_FILES[@]} -gt 0 ]]; then + for file in "${SOURCE_FILES[@]}"; + do + cp "$file" "$file""${BACKUP_EXTENSION}" 2>> "$LOGFILE" >&2 + sed -i "1,$(wc -l < "$file") s/^/#/" "$file" 2>> "$LOGFILE" >&2 + done + fi + + # Comment out cloud-init generated templates for sources + # CLOUD_INIT_FILES=(/etc/cloud/templates*/*.tmpl) 2>> "$LOGFILE" >&2 + # if [[ ${#CLOUD_INIT_FILES[@]} -gt 0 ]]; then + # for file in "${CLOUD_INIT_FILES[@]}"; + # do + # cp "$file" "$file""${BACKUP_EXTENSION}" 2>> "$LOGFILE" >&2 + # sed -i "1,$(wc -l < "$file") s/^/#/" "$file" 2>> "$LOGFILE" >&2 + # done + # fi + + if [[ $? -eq 0 ]]; then + op_log "Changing urls in sources.list to defaults" "FAILED" + else + op_log "Changing urls in sources.list to defaults" "FAILED" + fi fi -# Comment out cloud-init generated templates for sources -CLOUD_INIT_FILES=(/etc/cloud/templates*/*.tmpl) 2>> "$LOGFILE" >&2 -if [[ ${#CLOUD_INIT_FILES[@]} -gt 0 ]]; then - for file in "${CLOUD_INIT_FILES[@]}"; - do - mv "$file" "$file".bak 2>> "$LOGFILE" >&2 - sed -i "1,$(wc -l < "$file") s/^/#/" "$file" 2>> "$LOGFILE" >&2 - done -fi - -if [[ $? -eq 0 ]]; then - op_log "Changing urls in sources.list to defaults" "FAILED" -else - op_log "Changing urls in sources.list to defaults" "FAILED" -fi ############################################################## # Install required softwares ############################################################## + +op_log "Installing required softwares" { - op_log "Installing required softwares" - apt-get update && apt-get upgrade -y && apt-get install -y sudo curl screen 2>> "$LOGFILE" >&2 -} + apt-get update && apt-get upgrade -y && apt-get install -y sudo curl screen +} 2>> "$LOGFILE" >&2 + if [[ $? -eq 0 ]]; then op_log "Installing required softwares" "FAILED" else op_log "Installing required softwares" "FAILED" fi + +############################################################## +# Change root's password +############################################################## + +if [[ $RESET_ROOT_PWD == 'y' ]]; then + op_log " " + { + # Generate a 15 character random password + PASS_ROOT="$(< /dev/urandom tr -cd "[:alnum:]" | head -c 15)" || exit 1 + + file_log "Generated Root Password - ${PASS_ROOT}" + + # Change root's password + echo -e "${PASS_ROOT}\\n${PASS_ROOT}" | passwd + } 2>> "$LOGFILE" >&2 + + if [[ $? -eq 0 ]]; then + op_log "Changing root password" "SUCCESSFUL" + else + # Low priority - since we are disabling root login anyways + op_log "Changing root password" "FAILED" + fi +fi + + ############################################################## # Recap ##############################################################