diff --git a/init-linux-harden.sh b/init-linux-harden.sh index 03645c0..080897b 100644 --- a/init-linux-harden.sh +++ b/init-linux-harden.sh @@ -1,23 +1,33 @@ #!/etc/bin/env bash -# Accept user name as a script argument - # If no username provided - # generate a random username - all lowercase # Something important fails # Revert everything back to how it was -# Redirect every output to a logfile -# Ask the user to NOT logout yet - # Ask him to report back if he can login using the new user -with the ssh-private key -# If not - # Remove the SSH-only login and ask the user to login using password -# If he can - great - tell him to talk to the server provider's support to get help regarding SSH-only access - # Report the things - # Root password - # User Password - # User SSH-Private Key - # User SSH-Public key - # Display the root-user's new password on screen +# Ask the user to NOT logout yet and login as normal user + # If he can - great + # Remove the SSH-only login and ask the user to login using password + # Report + # Root password + # User Password + # User SSH-Private Key + # User SSH-Public key + # If not + # 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 +#Test + # 1 - Deb 9.x + # 2 - Deb 8.x + # 3 - Ubuntu 14.x + # 4 - Ubuntu 16.x + # 5 - Ubuntu 18.x + # DigitalOcean + # OVH + # Hetzner + +SCRIPT_NAME=server_harden +SCRIPT_VERSION=0.2 +LOGFILE=/tmp/"$SCRIPT_NAME"_v"$SCRIPT_VERSION".log ############################################################## # Basic checks before starting @@ -25,30 +35,22 @@ # No root - no good [ "$(id -u)" != "0" ] && { - echo "Error: You must be root to run this script, please login as root and execute the script again." + printf "ERROR: You must be root to run this script.\\nPlease login as root and execute the script again." exit 1 } # Check supported OSes -if [[ $(sed 's/\..*//' /etc/debian_version) -eq 8 ]]; then +if [[ $(cut -d. -f 1 < /etc/debian_version) -eq 8 ]]; then DEB_VER_STR="jessie" -elif [[ $(sed 's/\..*//' /etc/debian_version) -eq 9 ]]; then +elif [[ $(cut -d. -f 1 < /etc/debian_version) -eq 9 ]]; then DEB_VER_STR="stretch" else - printf "This version of Debian is NOT supported.\\n" + printf "This script only supports Debian Stretch (9.x) and Debian Jessie (8.x).\\n" + printf "Your OS is NOT supported.\\n" exit 1 fi -############################################################## -# Display what the script does -############################################################## - -# What to do if something fails - # Catastophic failure - # Ignorable failure -# Where to find the log file - ############################################################## # Gather info ############################################################## @@ -58,21 +60,34 @@ fi 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 [1-2]: " RESET_ROOT_PWD +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 -# Ask for a user name -echo "" -echo "A new non-root user will be created for you." -read -rp "Please provide a user name - " NORM_USER_NAME - -# If the user exists - ask for a different username -while [[ ! "$NORM_USER_NAME" ]] && [[ $(getent passwd "$NORM_USER_NAME" | wc -l) -gt 0 ]]; do - echo "User name either already exists or you provided an invalid username." - read -rp "Please provide a user name - " NORM_USER_NAME +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 +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 + ############################################################## # Log @@ -84,10 +99,9 @@ CRED="${CSI}1;31m" CGREEN="${CSI}1;32m" CVERTICAL="|" CHORIZONTAL="_" -SCRIPT_NAME=server_harden -SCRIPT_VERSION=0.2 -LOGFILE=/tmp/"$SCRIPT_NAME"_v"$SCRIPT_VERSION".log +# Reset privilous log file +printf "" > "$LOGFILE" function horizontal_fill() { local char=$1 @@ -118,6 +132,10 @@ function recap (){ } 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 + # Let user know nothing was changed + # if - Non-catastrophic failure - Inform user of side effects #local what_failed=$1 line_fill "$CHORIZONTAL" 60 @@ -130,22 +148,26 @@ function finally(){ line_fill "$CHORIZONTAL" 60 } -function log() { +function file_log(){ + printf "%s - %s\\n" "$(date '+%Y-%m-%d %H:%M:%S')" "$1" >> "$LOGFILE" +} + +function op_log() { local EVENT=$1 local RESULT=$2 if [ "$RESULT" = "SUCCESSFUL" ] then - printf "%30s %7s [${CGREEN}${RESULT}${CEND}]\\n" "$EVENT" " " - echo "$(date '+%Y-%m-%d %H:%M:%S')" - "$EVENT" - "$RESULT" >> "$LOGFILE" + printf "\r%30s %7s [${CGREEN}${RESULT}${CEND}]\\n" "$EVENT" " " + file_log "${EVENT} - ${RESULT}" elif [ "$RESULT" = "FAILED" ] then - printf "%30s %7s [${CRED}${RESULT}${CEND}]\\n" "$EVENT" " " + printf "\r%30s %7s [${CRED}${RESULT}${CEND}]\\n" "$EVENT" " " printf "\\n\\nPlease look at %s\\n\\n" "$LOGFILE" - echo "$(date '+%Y-%m-%d %H:%M:%S')" - "$EVENT" - "$RESULT" >> "$LOGFILE" + file_log "${EVENT} - ${RESULT}" else - printf "%30s %7s [${CRED}..${CEND}]\\r" "$EVENT" " " - echo "$(date '+%Y-%m-%d %H:%M:%S')" - "$EVENT" - "begin..." >> "$LOGFILE" + printf "%30s %7s [${CRED}..${CEND}]" "$EVENT" " " + file_log "${EVENT} - begin..." fi } @@ -162,32 +184,80 @@ else fi if [[ $SESSION_TYPE == "remote/ssh" ]]; then - printf "You are currently connected to an SSH session.\\n" + file_log "Connected through SSH session." else - printf "You are currently connected using password authentication.\\n" + file_log "Connected using password authentication." fi +############################################################## +# Display what the script does +############################################################## + +clear +cat < /dev/null - } + echo -e "${PASS_ROOT}\\n${PASS_ROOT}" | passwd + } 2>> "$LOGFILE" >&2 if [[ $? -eq 0 ]]; then - log "Changing root password" "SUCCESSFUL" + op_log "Changing root password" "SUCCESSFUL" else # Low priority - since we are disabling root login anyways - log "Changing root password" "FAILED" + op_log "Changing root password" "FAILED" fi fi @@ -196,22 +266,28 @@ fi # Create a normal user ############################################################## { - 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}" + 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 - echo -e "${USER_PASS}\\n${USER_PASS}" | adduser "$NORM_USER_NAME" -q --gecos "First Last,RoomNumber,WorkPhone,HomePhone" 2> /dev/null + echo -e "${USER_PASS}\\n${USER_PASS}" | adduser "$NORM_USER_NAME" -q --gecos "First Last,RoomNumber,WorkPhone,HomePhone" # Give root privilages to the above user usermod -aG sudo "$NORM_USER_NAME" || exit 1 -} +} 2>> "$LOGFILE" >&2 if [[ $? -eq 0 ]]; then - log "Creating new user" "SUCCESSFUL" + op_log "Creating new user" "SUCCESSFUL" else - log "Creating new user" "FAILED" + op_log "Creating new user" "FAILED" finally "CNU" exit 1; fi @@ -221,11 +297,13 @@ fi # Create SSH Key for the new user ############################################################## { - log "Creating SSH Key for new user" + op_log "Creating SSH Key for new user" shopt -s nullglob KEY_FILES=("$SSH_DIR"/"$NORM_USER_NAME".pem*) + #TODO - If SSH files already exist - rename them to .timestamp_bkp + # Create key file only if it does NOT exist if [[ ! ${KEY_FILES[0]} ]]; then SSH_DIR=/home/"$NORM_USER_NAME"/.ssh @@ -233,17 +311,24 @@ fi # 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 + # 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 fi -} +} 2>> "$LOGFILE" >&2 if [[ $? -eq 0 ]]; then - log "Creating SSH Key for new user" "SUCCESSFUL" + op_log "Creating SSH Key for new user" "SUCCESSFUL" else - log "Creating SSH Key for new user" "FAILED" + op_log "Creating SSH Key for new user" "FAILED" finally "CSK" exit 1; fi @@ -253,15 +338,18 @@ fi # Add generated key to authorized_keys file ############################################################## { - log "Adding SSH 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 # Insert the public key into "authoried_keys" file cat "${KEY_FILES[1]}" >> "$SSH_DIR"/authorized_keys || exit 1 -} +} 2>> "$LOGFILE" >&2 if [[ $? -eq 0 ]]; then - log "Adding SSH Key to 'authorized_keys' file" "SUCCESSFUL" + op_log "Adding SSH Key to 'authorized_keys' file" "SUCCESSFUL" else - log "Adding SSH Key to 'authorized_keys' file" "FAILED" + op_log "Adding SSH Key to 'authorized_keys' file" "FAILED" finally "ATAF" exit 1; fi @@ -271,7 +359,7 @@ fi # Secure authorized_keys file ############################################################## { - 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" && \ @@ -279,11 +367,14 @@ fi chmod 700 "$SSH_DIR" && \ chmod 400 "$SSH_DIR"/authorized_keys && \ chattr +i "$SSH_DIR"/authorized_keys -} +} 2>> "$LOGFILE" >&2 if [[ $? -eq 0 ]]; then - log "Securing 'authorized_keys' file" "SUCCESSFUL" + op_log "Securing 'authorized_keys' file" "SUCCESSFUL" else - log "Securing 'authorized_keys' file" "FAILED" + file_log "Setting restrictive permissions for '~/.ssh/' directory failed" + file_log "Please do 'ls -lAh ~/.ssh/' and check manually to see what went wrong." + file_log "Rest of the tasks will continue." + op_log "Securing 'authorized_keys' file" "FAILED" fi @@ -368,13 +459,13 @@ function set_config_key(){ } { - 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 # Remove root login - set_config_key "/etc/ssh/sshd_config" "PermitRootLogin" "no" + set_config_key "/etc/ssh/sshd_config" "PermitRootLogin" "no" # Disable password login set_config_key "/etc/ssh/sshd_config" "PasswordAuthentication" "no" @@ -383,11 +474,11 @@ function set_config_key(){ set_config_key "/etc/ssh/sshd_config" "AuthorizedKeysFile" '%h\/\.ssh\/authorized_keys' systemctl restart sshd -} +} 2>> "$LOGFILE" >&2 if [[ $? -eq 0 ]]; then - log "Enabling SSH-only login" "SUCCESSFUL" + op_log "Enabling SSH-only login" "SUCCESSFUL" else - log "Enabling SSH-only login" "FAILED" + op_log "Enabling SSH-only login" "FAILED" finally "ESOL" exit 1; fi @@ -398,10 +489,10 @@ fi ############################################################## # Low priority - But what to do if it fails??? -log "Changing urls in sources.list to defaults" +op_log "Changing urls in sources.list to defaults" -mv /etc/apt/sources.list /etc/apt/sources.list.bak -sed -i "1,$(wc -l < /etc/apt/sources.list.bak) s/^/#/" /etc/apt/sources.list.bak +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 # Default sources list for debian cat < /etc/apt/sources.list || exit 1 @@ -421,37 +512,44 @@ 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) +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 - sed -i "1,$(wc -l < "$file") s/^/#/" "$file" >&2 /dev/null + mv "$file" "$file".bak 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) +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 - sed -i "1,$(wc -l < "$file") s/^/#/" "$file" >&2 /dev/null + mv "$file" "$file".bak 2>> "$LOGFILE" >&2 + sed -i "1,$(wc -l < "$file") s/^/#/" "$file" 2>> "$LOGFILE" >&2 done fi if [[ $? -eq 0 ]]; then - log "Changing urls in sources.list to defaults" "FAILED" + op_log "Changing urls in sources.list to defaults" "FAILED" else - log "Changing urls in sources.list to defaults" "FAILED" + op_log "Changing urls in sources.list to defaults" "FAILED" fi ############################################################## # Install required softwares ############################################################## -apt-get update && apt-get upgrade -y && apt-get install -y sudo curl screen - +{ + op_log "Installing required softwares" + 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 ############################################################## # Recap