Skip to content

Instantly share code, notes, and snippets.

@NotYusta
Last active March 30, 2025 09:13
Show Gist options
  • Save NotYusta/9e0e52eb6bc4a45d522529df79abe1d3 to your computer and use it in GitHub Desktop.
Save NotYusta/9e0e52eb6bc4a45d522529df79abe1d3 to your computer and use it in GitHub Desktop.
SSH Strict Authentication (MFA)
#!/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