Last active
March 30, 2025 09:13
-
-
Save NotYusta/9e0e52eb6bc4a45d522529df79abe1d3 to your computer and use it in GitHub Desktop.
SSH Strict Authentication (MFA)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# πΉ Backup directory for rollback | |
BACKUP_DIR="/root/.ssh-security-backup" | |
SUDO_GROUP="sudo" | |
generate_password() { | |
local password length num_count special_count char_count | |
while true; do | |
# Random total length between 24 and 32 | |
length=$((RANDOM % 9 + 24)) | |
# Ensure at least 3 numbers and 3 special characters, but random within limits | |
num_count=$((RANDOM % (length / 3) + 3)) # At least 3 numbers | |
special_count=$((RANDOM % (length / 3) + 3)) # At least 3 special characters | |
# Ensure we do not exceed total length | |
char_count=$((length - num_count - special_count)) | |
# Generate random characters for each category | |
numbers=$(< /dev/urandom tr -dc '0-9' | head -c "$num_count") | |
specials=$(< /dev/urandom tr -dc '@#!_$%^&*()-=' | head -c "$special_count") | |
alphabets=$(< /dev/urandom tr -dc 'A-Za-z' | head -c "$char_count") | |
# Combine and shuffle | |
password="${numbers}${specials}${alphabets}" | |
password=$(echo "$password" | fold -w1 | shuf | tr -d '\n') | |
if validate_password "$password"; then | |
echo "$password" | |
return | |
fi | |
done | |
} | |
validate_password() { | |
local pass="$1" | |
[[ ${#pass} -ge 24 ]] && | |
[[ "$pass" =~ [A-Z] ]] && [[ "$pass" =~ [a-z] ]] && | |
[[ $(grep -o '[0-9]' <<< "$pass" | wc -l) -ge 3 ]] && | |
[[ $(grep -o '[-@#!_$%^&*()=]' <<< "$pass" | wc -l) -ge 3 ]] | |
} | |
# πΉ Function: Restore backups if script fails | |
restore_backup() { | |
echo -e "π Restoring backups..." | |
[[ -f "$BACKUP_DIR/jail.conf.bak" ]] && cp -f "$BACKUP_DIR/jail.conf.bak" /etc/fail2ban/jail.conf | |
[[ -f "$BACKUP_DIR/sshd_config.bak" ]] && cp -f "$BACKUP_DIR/sshd_config.bak" /etc/ssh/sshd_config | |
[[ -f "$BACKUP_DIR/root_shadow.bak" ]] && cp -f "$BACKUP_DIR/root_shadow.bak" /etc/shadow | |
[[ -f "$BACKUP_DIR/root_authorized_keys.bak" ]] && cp -f "$BACKUP_DIR/root_authorized_keys.bak" /root/.ssh/authorized_keys | |
systemctl restart fail2ban sshd | |
echo -e "π System reverted." | |
exit 1 | |
} | |
handle_root_ssh() { | |
# Ask if user wants to delete root's SSH keys | |
if prompt_yes_no "Do you want to delete the SSH keys for root?" "y"; then | |
# Delete root's SSH keys if they exist | |
if [ -d /root/.ssh ]; then | |
rm -rf /root/.ssh/* | |
echo -e "β Root's SSH keys have been deleted." | |
else | |
echo -e "β οΈ No SSH keys found for root." | |
fi | |
fi | |
# Ask if user wants to disable password login for root | |
if prompt_yes_no "Do you want to disable password authentication for root?" "y"; then | |
passwd -l root | |
echo -e "β Password authentication for root has been disabled." | |
fi | |
} | |
# πΉ Function: Prompt user with default value | |
prompt_yes_no() { | |
local PROMPT="$1" | |
local DEFAULT="${2:-y}" | |
read -rp "$PROMPT [y/n] (default: $DEFAULT): " REPLY </dev/tty | |
REPLY=${REPLY:-$DEFAULT} | |
[[ "$REPLY" =~ ^[Yy]$ ]] | |
} | |
handle_users() { | |
echo -e "π Checking users on the system..." | |
# List all users except for root and the specified username | |
USERS=$(getent passwd | awk -F: '$3 >= 1000 && $1 != "root" && $1 != "nobody" && $1 != "'$USERNAME'" {print $1}') | |
for user in $USERS; do | |
echo -e "User '$user' found. Do you want to delete or disable this user?" | |
if prompt_yes_no "Delete or disable user '$user'?" "n"; then | |
read -p "Do you want to delete (d) or disable (x) the user? [d/x]: " ACTION | |
case "$ACTION" in | |
d|D) | |
userdel -r "$user" | |
echo -e "β User '$user' deleted." | |
;; | |
x|X) | |
usermod -L "$user" | |
echo -e "β User '$user' disabled." | |
;; | |
*) | |
echo -e "β Invalid choice for user '$user'. Skipping..." | |
;; | |
esac | |
else | |
echo -e "β Keeping user '$user'." | |
fi | |
done | |
} | |
# πΉ Set up Google Authenticator | |
setup_google_authenticator() { | |
# Check if Google Authenticator is already set up for the user | |
if [ -f "/home/$USERNAME/.google_authenticator" ]; then | |
echo "Google Authenticator is already set up for $USERNAME." | |
# Prompt to replace the existing setup | |
if prompt_yes_no "Do you want to replace it?" "n"; then | |
# Backup the existing Google Authenticator file | |
mv "/home/$USERNAME/.google_authenticator" "/home/$USERNAME/.google_authenticator.bak" | |
echo "Existing Google Authenticator setup has been backed up." | |
# Set up new Google Authenticator | |
su - "$USERNAME" -c "google-authenticator -t -d -f -r 3 -R 30 -w 3" | |
echo "Google Authenticator has been replaced for $USERNAME." | |
else | |
echo "Skipping Google Authenticator setup for $USERNAME." | |
fi | |
else | |
# Set up Google Authenticator for the user if not already set up | |
su - "$USERNAME" -c "google-authenticator -t -d -f -r 3 -R 30 -w 3" | |
echo "Google Authenticator has been set up for $USERNAME." | |
fi | |
} | |
# πΉ Install necessary packages | |
install_packages() { | |
if [[ -f /etc/os-release ]]; then | |
source /etc/os-release | |
case "$ID" in | |
ubuntu) | |
add-apt-repository universe -y | |
apt update -y | |
apt install -y nano libpam-google-authenticator fail2ban crudini openssh-server | |
;; | |
debian) | |
apt update -y | |
apt install -y nano libpam-google-authenticator fail2ban crudini openssh-server | |
;; | |
rocky|almalinux|centos|fedora) | |
SUDO_GROUP="wheel" | |
dnf update -y | |
dnf install -y epel-release | |
dnf install -y nano google-authenticator fail2ban crudini openssh-server | |
echo "Clearing SSH Cache" | |
rm -rf /var/lib/sss/db/* && systemctl restart sssd | |
;; | |
*) | |
echo -e "β Unsupported OS: $ID" | |
exit 1 | |
;; | |
esac | |
else | |
echo -e "β Unable to detect OS." | |
exit 1 | |
fi | |
if [ -f /etc/firewalld/services/00-firewalld.conf ]; then | |
sudo rm /etc/firewalld/services/00-firewalld.conf | |
fi | |
} | |
mkdir -p "$BACKUP_DIR" | |
# πΉ Trap CTRL+C to restore backup | |
trap restore_backup SIGINT | |
# πΉ Backup key files & configs before modifying | |
[ -f /etc/fail2ban/jail.conf ] && cp -f /etc/fail2ban/jail.conf "$BACKUP_DIR/jail.conf.bak" | |
[ -f /etc/ssh/sshd_config ] && cp -f /etc/ssh/sshd_config "$BACKUP_DIR/sshd_config.bak" | |
[ -f /etc/shadow ] && cp -f /etc/shadow "$BACKUP_DIR/root_shadow.bak" | |
[ -f /root/.ssh/authorized_keys ] && cp -f /root/.ssh/authorized_keys "$BACKUP_DIR/root_authorized_keys.bak" | |
install_packages | |
clear | |
# πΉ Default settings | |
DEFAULT_BAN_TIME="24h" | |
DEFAULT_MAX_RETRY=3 | |
DEFAULT_USERNAME="linux" | |
# πΉ Prompt for settings | |
until read -rp "Enter ban time (e.g., 24h, 30m, 3600s) [default: $DEFAULT_BAN_TIME]: " BAN_TIME </dev/tty && \ | |
BAN_TIME=${BAN_TIME:-$DEFAULT_BAN_TIME} && [[ "$BAN_TIME" =~ ^[0-9]+[hmsd]$ ]]; do | |
echo -e "β Invalid input! Use format: 24h, 30m, 3600s." | |
done | |
until read -rp "Enter max retry attempts [default: $DEFAULT_MAX_RETRY]: " MAX_RETRY </dev/tty && \ | |
MAX_RETRY=${MAX_RETRY:-$DEFAULT_MAX_RETRY} && [[ "$MAX_RETRY" =~ ^[0-9]+$ ]]; do | |
echo -e "β Invalid input! Must be a positive number." | |
done | |
read -rp "Enter username [default: $DEFAULT_USERNAME]: " USERNAME </dev/tty | |
USERNAME=${USERNAME:-$DEFAULT_USERNAME} | |
# πΉ Create user and grant sudo access | |
if id "$USERNAME" &>/dev/null; then | |
echo -e "β οΈ User '$USERNAME' already exists." | |
if prompt_yes_no "Do you want to delete the SSH keys and Google Auth configurations and continue?" "n"; then | |
echo -e "β³ Deleting SSH keys and Google Auth configurations for '$USERNAME'." | |
# πΉ Remove SSH keys (typically located in ~/.ssh/) | |
rm -rf "/home/$USERNAME/.ssh" | |
echo -e "β SSH keys for '$USERNAME' removed." | |
# πΉ Remove Google Auth configuration (assuming it's in ~/.google_authenticator/) | |
rm -rf "/home/$USERNAME/.google_authenticator" | |
echo -e "β Google Auth configuration for '$USERNAME' removed." | |
# πΉ Proceed to create new user (if needed) | |
# Ensure that the user still exists after cleanup | |
if ! id "$USERNAME" &>/dev/null; then | |
useradd -m -s /bin/bash "$USERNAME" | |
usermod -aG "$SUDO_GROUP" "$USERNAME" | |
echo -e "β User '$USERNAME' created and granted sudo ('$SUDO_GROUP') access." | |
fi | |
elif prompt_yes_no "Do you want to continue with the existing user without changes?" "y"; then | |
echo -e "β Continuing with existing user '$USERNAME'." | |
echo -e "β User '$USERNAME' granted sudo ('$SUDO_GROUP') access." | |
else | |
echo -e "β Exiting. User creation aborted." | |
exit 1 | |
fi | |
else | |
echo -e "β User '$USERNAME' does not exist. Creating the user." | |
# πΉ Create user and grant sudo access | |
useradd -m -s /bin/bash "$USERNAME" | |
usermod -aG "$SUDO_GROUP" "$USERNAME" | |
echo -e "β User '$USERNAME' created and granted sudo access." | |
fi | |
sed -i '/auth required pam_google_authenticator.so/d' /etc/pam.d/sshd | |
sed -i '1i auth required pam_google_authenticator.so' /etc/pam.d/sshd | |
# πΉ Set up SSH keys | |
SSH_DIR="/home/$USERNAME/.ssh" | |
mkdir -p "$SSH_DIR" | |
chmod 700 "$SSH_DIR" | |
chown "$USERNAME:$USERNAME" "$SSH_DIR" | |
SUPPORTED_ALGOS=("ed25519" "rsa" "ecdsa" "dsa") | |
DETECTED_ALGO="ed25519" | |
# πΉ Check existing SSH private keys | |
EXISTING_PRIVATE_KEY=$(find "$SSH_DIR" -type f -exec grep -l -- '-----BEGIN OPENSSH PRIVATE KEY-----' {} \; | head -n 1) | |
# Check if there are any existing SSH private keys | |
if [[ -n "$EXISTING_PRIVATE_KEY" ]]; then | |
if prompt_yes_no "Existing SSH private key found ($EXISTING_PRIVATE_KEY). Replace it?" "n"; then | |
rm -f "$EXISTING_PRIVATE_KEY" "${EXISTING_PRIVATE_KEY%.pub}" | |
EXISTING_PRIVATE_KEY="" | |
echo -e "β SSH private key has been removed." | |
else | |
echo -e "β Keeping existing private key." | |
fi | |
else | |
echo -e "β No existing SSH private key found." | |
fi | |
if [[ -z "$EXISTING_PRIVATE_KEY" ]]; then | |
read -rp "Enter SSH key algorithm (ed25519, rsa, ecdsa, dsa) [default: $DETECTED_ALGO]: " SSH_ALGO </dev/tty | |
SSH_ALGO=${SSH_ALGO:-$DETECTED_ALGO} | |
[[ ! " ${SUPPORTED_ALGOS[*]} " =~ " ${SSH_ALGO} " ]] && SSH_ALGO="ed25519" | |
if prompt_yes_no "Do you want to use a passphrase for the SSH key?" "y"; then | |
if prompt_yes_no "Do you want to generate a random passphrase?" "y"; then | |
SSH_PASSPHRASE=$(generate_password) | |
else | |
while true; do | |
read -s -rp "Enter passphrase: " SSH_PASSPHRASE | |
echo | |
if validate_password "$SSH_PASSPHRASE"; then | |
break | |
else | |
echo "Passphrase must be at least 24 characters long, contain at least one uppercase letter, one lowercase letter, at least 3 numbers, and at least 3 special characters (@#!_+)." | |
fi | |
done | |
fi | |
else | |
SSH_PASSPHRASE="" # No passphrase | |
fi | |
read -rp "Enter comment for SSH key: " SSH_COMMENT </dev/tty | |
ssh-keygen -t "$SSH_ALGO" -C "$SSH_COMMENT" -N "$SSH_PASSPHRASE" -f "$SSH_DIR/id_$SSH_ALGO" | |
cat "$SSH_DIR/id_$SSH_ALGO.pub" >> "$SSH_DIR/authorized_keys" | |
chmod 600 "$SSH_DIR/authorized_keys" | |
chown -R "$USERNAME:$USERNAME" "$SSH_DIR" | |
EXISTING_PRIVATE_KEY="$SSH_DIR/id_$SSH_ALGO" | |
fi | |
# Call the function to enable Google Authenticator | |
if prompt_yes_no "Enable Google Authenticator for $USERNAME?" "y"; then | |
setup_google_authenticator | |
fi | |
# πΉ Configure Fail2Ban | |
crudini --set /etc/fail2ban/jail.local sshd enabled true | |
crudini --set /etc/fail2ban/jail.local sshd bantime "$BAN_TIME" | |
crudini --set /etc/fail2ban/jail.local sshd maxretry "$MAX_RETRY" | |
crudini --set /etc/fail2ban/jail.local sshd banaction iptables-multiport | |
if fail2ban-client -d; then | |
systemctl stop fail2ban | |
sleep 1s | |
systemctl enable fail2ban --now | |
echo -e "β Fail2Ban configuration applied successfully." | |
else | |
echo -e "β Fail2Ban configuration error. Reverting..." | |
restore_backup | |
fi | |
# πΉ Secure SSH Configuration | |
sed -i '/^Include/d' /etc/ssh/sshd_config | |
sed -i '/^KbdInteractiveAuthentication/d' /etc/ssh/sshd_config | |
echo -e "KbdInteractiveAuthentication yes" >> /etc/ssh/sshd_config | |
sed -i '/^PermitRootLogin/d' /etc/ssh/sshd_config | |
echo -e "PermitRootLogin no" >> /etc/ssh/sshd_config | |
sed -i '/^PasswordAuthentication/d' /etc/ssh/sshd_config | |
echo -e "PasswordAuthentication no" >> /etc/ssh/sshd_config | |
sed -i '/^AuthenticationMethods/d' /etc/ssh/sshd_config | |
echo -e "AuthenticationMethods publickey,keyboard-interactive" >> /etc/ssh/sshd_config | |
sed -i '/^UsePAM/d' /etc/ssh/sshd_config | |
echo -e "UsePAM yes" >> /etc/ssh/sshd_config | |
sed -i '/^ChallengeResponseAuthentication/d' /etc/ssh/sshd_config | |
echo -e "ChallengeResponseAuthentication yes" >> /etc/ssh/sshd_config | |
if prompt_yes_no "Do you want to generate a random password?" "y"; then | |
USER_PASSWORD=$(generate_password) | |
else | |
while true; do | |
read -s -p "Enter password: " USER_PASSWORD | |
echo | |
if validate_password "$USER_PASSWORD"; then | |
break | |
else | |
echo "Password must be at least 24 characters long, contain at least one uppercase letter, one lowercase letter, at least 3 numbers, and at least 3 special characters (@#!_+)." | |
fi | |
done | |
fi | |
echo -e "$USER_PASSWORD\n$USER_PASSWORD" | passwd "$USERNAME" | |
echo "Password set for $user." | |
# πΉ Validate SSH config before restarting | |
if sshd -t; then | |
systemctl restart sshd | |
echo -e "β SSH configuration applied." | |
else | |
echo -e "β SSH configuration error. Reverting..." | |
restore_backup | |
fi | |
handle_users | |
handle_root_ssh | |
clear | |
# πΉ Display SSH Key (last step) | |
echo -e "β User '$USERNAME' created with sudo access." | |
echo -e "π‘οΈ Password: $USER_PASSWORD" | |
echo "" | |
echo -e "π Setup complete! Here is your private SSH key:" | |
cat $EXISTING_PRIVATE_KEY | |
if [[ -n "$SSH_PASSPHRASE" ]]; then | |
echo -e "π‘οΈ Passphrase: $SSH_PASSPHRASE" | |
fi | |
echo "" | |
echo -e "π Please save the key and the password securely." | |
echo -e "βοΈ SSH Hardening Script Made by: @Yusta [https://github.com/NotYusta]" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment