Last active
July 14, 2024 22:27
-
-
Save loopyd/b9136aa2837d33131a96b0ff4217e2f5 to your computer and use it in GitHub Desktop.
[bash] Do 98% of the work configuring SSH secure passwordless login on your new VPS
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
#!/usr/bin/env bash | |
# config_ssh.sh - A script to set up your VPS SSH configuration | |
# Author: loopyd <[email protected]> 2024 | GPL3.0 Permassive License | |
# Run this script on your shiny new VPS to harden it and set up SSH keys and passwords. | |
# Note: If you are using a hosting proivder such as hostinger, you need to add the .pub keys in | |
# the dashbaord, as an overlayfs by the provider is used to overwrite the ssh configuration. | |
# | |
# If you experience errors trying to log in, try to add a ~/.ssh/config on your host with the following: | |
# | |
# Host x.x.x.x | |
# IdentityFile ~/.ssh/id_ed25519_hostname_username | |
# IdentitiesOnly yes | |
# User username | |
# HostName x.x.x.x | |
# | |
# And for root login: | |
# | |
# Host x.x.x.x | |
# IdentityFile ~/.ssh/id_ed25519_hostname_root | |
# IdentitiesOnly yes | |
# User root | |
# HostName x.x.x.x | |
# | |
# To troubleshoot further, you can clear out your ssh-agent keys | |
# | |
# eval "$(ssh-agent)" | |
# ssh-add -D | |
# | |
# Then, add the keys back to the agent (for example, when configuring vscode remote or github): | |
# | |
# ssh-add ~/.ssh/id_hostname_ed25519_username | |
# ssh-add ~/.ssh/id_hostname_ed25519_root | |
CSCRIPT_DIR=$(cd $(dirname $0) && pwd) | |
# Set the following variables to customize the script | |
SSH_HOSTNAME=${SSH_HOSTNAME:-} | |
SSH_PORT=${SSH_PORT:-22} | |
SSH_IP=${SSH_IP:-} | |
SSH_ROOT_PASSWORD=${SSH_PASSWORD:-} | |
SSH_USER_NAME=${SSH_USER_NAME:-} | |
SSH_USER_PASSWORD=${SSH_PASSWORD:-} | |
#################################################################################################### | |
# logging functions | |
#################################################################################################### | |
# colors | |
C_RED=$(tput setaf 1) | |
C_GREEN=$(tput setaf 2) | |
C_YELLOW=$(tput setaf 3) | |
C_BLUE=$(tput setaf 4) | |
C_MAGENTA=$(tput setaf 5) | |
C_CYAN=$(tput setaf 6) | |
C_WHITE=$(tput setaf 7) | |
C_RESET=$(tput sgr0) | |
C_BOLD=$(tput bold) | |
# output functions | |
function error() { | |
echo "${C_RED}${C_BOLD}ERROR: $*${C_RESET}" >&2 | |
} | |
function warning() { | |
echo "${C_YELLOW}${C_BOLD}WARNING: $*${C_RESET}" >&2 | |
} | |
function info() { | |
echo "${C_CYAN}${C_BOLD}INFO: $*${C_RESET}" | |
} | |
function success() { | |
echo "${C_GREEN}${C_BOLD}SUCCESS: $*${C_RESET}" | |
} | |
#################################################################################################### | |
# Helper functions | |
#################################################################################################### | |
function is_pkg_installed() { | |
local _pkg=$1 | |
if [ -z "${_pkg}" ]; then | |
error "Package name is not set." | |
return 1 | |
fi | |
if ! dpkg -l | grep -q "${_pkg}"; then | |
return 1 | |
fi | |
} | |
function install_pkgs() { | |
local _pkgs=($@) | |
if [ -z "${_pkgs}" ]; then | |
error "Package names are not set." | |
return 1 | |
fi | |
for _pkg in "${_pkgs[@]}"; do | |
if ! is_pkg_installed "${_pkg}"; then | |
shell_run sudo apt-get install -y "${_pkg}" || return $? | |
fi | |
done | |
return 0 | |
} | |
function validate_password() { | |
if [ -z "$1" ]; then | |
error "Password is not set, exiting" | |
return 1 | |
fi | |
if [ ${#1} -lt 8 ]; then | |
error "Password is too short, exiting" | |
return 1 | |
fi | |
if ! echo "$1" | grep -q '[0-9]'; then | |
error "Password must contain at least one number" | |
return 1 | |
fi | |
if ! echo "$1" | grep -q '[a-z]'; then | |
error "Password must contain at least one lowercase letter" | |
return 1 | |
fi | |
if ! echo "$1" | grep -q '[A-Z]'; then | |
error "Password must contain at least one uppercase letter" | |
return 1 | |
fi | |
if ! echo "$1" | grep -q '[!@#$%^&*()_+]'; then | |
error "Password must contain at least one special character" | |
return 1 | |
fi | |
return 0 | |
} | |
function get_password_from_file() { | |
local _hostname=$1 | |
if [ -z "${_hostname}" ]; then | |
error "Hostname is not set." | |
return 1 | |
fi | |
shift 1 | |
local _user=$1 | |
if [ -z "${_user}" ]; then | |
error "User is not set." | |
return 1 | |
fi | |
shift 1 | |
local _file | |
_file="${CSCRIPT_DIR}/pwd_${_hostname}_${_user}" | |
if [ ! -f "${_file}" ]; then | |
error "Password file not found: ${_file}" | |
return 1 | |
fi | |
cat "${_file}" | |
} | |
function shell_run() { | |
local _cmd=() | |
_cmd=($@) | |
if [ -z "${_cmd}" ]; then | |
return 1 | |
fi | |
eval "${_cmd[@]}" || return $? | |
} | |
function ssh_run_root() { | |
local _cmd=() | |
_cmd=($*) | |
sshpass -p "${SSH_ROOT_PASSWORD}" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p "${SSH_PORT}" root@${SSH_IP} "${_cmd[*]}" || { | |
error "Failed to run command: ${_cmd[*]}" | |
return 1 | |
} | |
return 0 | |
} | |
function ssh_run_user() { | |
local _cmd=() | |
_cmd=($@) | |
if [ -z "${_cmd[@]}" ]; then | |
return 1 | |
fi | |
sshpass -p "${SSH_USER_PASSWORD}" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p "${SSH_PORT}" ${SSH_USER_NAME}@${SSH_IP} "${_cmd[*]}" || { | |
error "Failed to run command: ${_cmd[*]}" | |
return 1 | |
} | |
return 0 | |
} | |
# Ensure required packages are installed | |
install_pkgs sshpass pwgen | |
# Ensure passwords are set and saved and generate and saved if not | |
if [ ! -f "${CSCRIPT_DIR}/pwd_${SSH_HOSTNAME}_root" ]; then | |
if [ -z "${SSH_ROOT_PASSWORD}" ]; then | |
echo "- Generating root password" | |
SSH_ROOT_PASSWORD=$(pwgen -sync1 -r \$\|\"\'\`\&\[\]\{\} 32) | |
fi | |
echo " - Writing root password to file: ${CSCRIPT_DIR}/pwd_${SSH_HOSTNAME}_root" | |
echo "${SSH_ROOT_PASSWORD}" >"${CSCRIPT_DIR}/pwd_${SSH_HOSTNAME}_root" | |
else | |
SSH_ROOT_PASSWORD=$(get_password_from_file ${SSH_HOSTNAME} root) || { | |
exit $? | |
} | |
fi | |
if [ ! -f "${CSCRIPT_DIR}/pwd_${SSH_HOSTNAME}_${SSH_USER_NAME}" ]; then | |
if [ -z "${SSH_USER_PASSWORD}" ]; then | |
echo "- Generating user password" | |
SSH_USER_PASSWORD=$(pwgen -sync1 -r \$\|\"\'\`\&\[\]\{\} 32) | |
fi | |
echo " - Writing user password to file: ${CSCRIPT_DIR}/pwd_${SSH_HOSTNAME}_${SSH_USER_NAME}" | |
echo "${SSH_USER_PASSWORD}" >"${CSCRIPT_DIR}/pwd_${SSH_HOSTNAME}_${SSH_USER_NAME}" | |
else | |
SSH_USER_PASSWORD=$(get_password_from_file ${SSH_HOSTNAME} ${SSH_USER_NAME}) || { | |
exit $? | |
} | |
fi | |
# Generate SSH keys if they don't exist | |
if [ ! -f "${CSCRIPT_DIR}/id_ed25519_${SSH_HOSTNAME}_root" ]; then | |
echo " - Generating SSH key for root" | |
ssh-keygen -t ed25519 -f ./id_ed25519_${SSH_HOSTNAME}_root -N "$(get_password_from_file ${SSH_HOSTNAME} root)" | |
fi | |
if [ ! -f "${CSCRIPT_DIR}/id_ed25519_${SSH_HOSTNAME}_${SSH_USER_NAME}" ]; then | |
echo " - Generating SSH key for user: ${SSH_USER_NAME}" | |
ssh-keygen -t ed25519 -f ./id_ed25519_${SSH_HOSTNAME}_${SSH_USER_NAME} -N "$(get_password_from_file ${SSH_HOSTNAME} ${SSH_USER_NAME})" | |
fi | |
# Create SSH directories and files on local machine if they don't exist | |
if [ ! -d "$HOME/.ssh" ]; then | |
echo "- Creating directory: $HOME/.ssh" | |
mkdir -p $HOME/.ssh | |
fi | |
if [ ! -f "$HOME/.ssh/authorized_keys" ]; then | |
echo "- Creating file: $HOME/.ssh/authorized_keys" | |
touch $HOME/.ssh/authorized_keys | |
fi | |
# Add keys to authorized_keys on local machine, if not already there | |
if ! grep -q "$(cat ${CSCRIPT_DIR}/id_ed25519_${SSH_HOSTNAME}_root.pub)" $HOME/.ssh/authorized_keys; then | |
echo " - Adding key for user: root to authorized_keys on local machine" | |
cat ${CSCRIPT_DIR}/id_ed25519_${SSH_HOSTNAME}_root.pub >>$HOME/.ssh/authorized_keys | |
fi | |
if ! grep -q "$(cat ${CSCRIPT_DIR}/id_ed25519_${SSH_HOSTNAME}_${SSH_USER_NAME}.pub)" $HOME/.ssh/authorized_keys; then | |
echo " - Adding key for user: ${SSH_USER_NAME} to authorized_keys on local machine" | |
cat ${CSCRIPT_DIR}/id_ed25519_${SSH_HOSTNAME}_${SSH_USER_NAME}.pub >>$HOME/.ssh/authorized_keys | |
fi | |
# Copy keys to local machine's SSH directory | |
echo " - Copying key for user: root to local machine" | |
cp -f ${CSCRIPT_DIR}/id_ed25519_${SSH_HOSTNAME}_root ~/.ssh/id_ed25519_${SSH_HOSTNAME}_root | |
cp -f ${CSCRIPT_DIR}/id_ed25519_${SSH_HOSTNAME}_root.pub ~/.ssh/id_ed25519_${SSH_HOSTNAME}_root.pub | |
echo " - Copying key for user: ${SSH_USER_NAME} to local machine" | |
cp -f ${CSCRIPT_DIR}/id_ed25519_${SSH_HOSTNAME}_${SSH_USER_NAME} ~/.ssh/id_ed25519_${SSH_HOSTNAME}_${SSH_USER_NAME} | |
cp -f ${CSCRIPT_DIR}/id_ed25519_${SSH_HOSTNAME}_${SSH_USER_NAME}.pub ~/.ssh/id_ed25519_${SSH_HOSTNAME}_${SSH_USER_NAME}.pub | |
if [ -f "$HOME/.ssh/known_hosts" ]; then | |
ssh-keygen -f "$HOME/.ssh/known_hosts" -R "${SSH_IP}" &>/dev/null | |
fi | |
SSH_ROOT_KEY=$(cat ${CSCRIPT_DIR}/id_ed25519_${SSH_HOSTNAME}_root) | |
SSH_ROOT_KEY_PUB=$(cat ${CSCRIPT_DIR}/id_ed25519_${SSH_HOSTNAME}_root.pub) | |
SSH_USER_KEY=$(cat ${CSCRIPT_DIR}/id_ed25519_${SSH_HOSTNAME}_${SSH_USER_NAME}) | |
SSH_USER_KEY_PUB=$(cat ${CSCRIPT_DIR}/id_ed25519_${SSH_HOSTNAME}_${SSH_USER_NAME}.pub) | |
SSH_USER_KEY_PUB=$(cat ${CSCRIPT_DIR}/id_ed25519_${SSH_HOSTNAME}_${SSH_USER_NAME}.pub) | |
ssh_run_root "/usr/bin/env bash -s" <<ENDSSH | |
function exit_trap() { | |
local _exit_code=$? | |
if [ ${_exit_code} -ne 0 ]; then | |
echo "Script exited with error code: ${_exit_code}" | |
exit ${_exit_code} | |
else | |
echo "Script exited successfully." | |
exit 0 | |
fi | |
} | |
trap exit_trap EXIT ERR SIGINT SIGTERM | |
echo " - Updating password for: root" | |
echo -e "${SSH_ROOT_PASSWORD}\n${SSH_ROOT_PASSWORD}" | passwd | |
if ! id -u ${SSH_USER_NAME} &>/dev/null; then | |
echo " - Creating user: ${SSH_USER_NAME}" | |
useradd -m -s /bin/bash ${SSH_USER_NAME} -p "$(openssl passwd -1 ${SSH_USER_PASSWORD})" | |
fi | |
echo " - Updating password for: ${SSH_USER_NAME}" | |
echo -e "${SSH_USER_PASSWORD}\n${SSH_USER_PASSWORD}" | passwd ${SSH_USER_NAME} | |
if [ ! -d "/root/.ssh" ]; then | |
echo "- Creating directory: /root/.ssh" | |
mkdir -p /root/.ssh | |
fi | |
if [ ! -f "/root/.ssh/authorized_keys" ]; then | |
echo "- Creating file: /root/.ssh/authorized_keys" | |
touch /root/.ssh/authorized_keys | |
fi | |
if [ ! -d "/home/${SSH_USER_NAME}/.ssh" ]; then | |
echo "- Creating directory: /home/${SSH_USER_NAME}/.ssh" | |
mkdir -p /home/${SSH_USER_NAME}/.ssh | |
chown ${SSH_USER_NAME}:${SSH_USER_NAME} /home/${SSH_USER_NAME}/.ssh | |
fi | |
if [ ! -f "/home/${SSH_USER_NAME}/.ssh/authorized_keys" ]; then | |
echo "- Creating file: /home/${SSH_USER_NAME}/.ssh/authorized_keys" | |
touch /home/${SSH_USER_NAME}/.ssh/authorized_keys | |
chown ${SSH_USER_NAME}:${SSH_USER_NAME} /home/${SSH_USER_NAME}/.ssh/authorized_keys | |
fi | |
if [ ! -f "/root/.ssh/id_ed25519_${SSH_HOSTNAME}_root" ]; then | |
echo "- Copying private key for user: root to remote server" | |
echo '${SSH_ROOT_KEY}' >/root/.ssh/id_ed25519_${SSH_HOSTNAME}_root | |
fi | |
if [ ! -f "/root/.ssh/id_ed25519_${SSH_HOSTNAME}_root.pub" ]; then | |
echo "- Copying public key for user: root to remote server" | |
echo '${SSH_ROOT_KEY_PUB}' >/root/.ssh/id_ed25519_${SSH_HOSTNAME}_root.pub | |
fi | |
if [ ! -f "/home/${SSH_USER_NAME}/.ssh/id_ed25519_${SSH_HOSTNAME}_${SSH_USER_NAME}" ]; then | |
echo "- Copying private key for user: ${SSH_USER_NAME} to remote server" | |
echo '${SSH_USER_KEY}' >/home/${SSH_USER_NAME}/.ssh/id_ed25519_${SSH_HOSTNAME}_${SSH_USER_NAME} | |
fi | |
if [ ! -f "/home/${SSH_USER_NAME}/.ssh/id_ed25519_${SSH_HOSTNAME}_${SSH_USER_NAME}.pub" ]; then | |
echo "- Copying public key for user: ${SSH_USER_NAME} to remote server" | |
echo '${SSH_USER_KEY_PUB}' >/home/${SSH_USER_NAME}/.ssh/id_ed25519_${SSH_HOSTNAME}_${SSH_USER_NAME}.pub | |
fi | |
# Generate host keys if they don't exist | |
if [ ! -f "/etc/ssh/ssh_host_rsa_key.pub" ]; then | |
echo "- Generating host key: /etc/ssh/ssh_host_rsa_key" | |
ssh-keygen -y -f /etc/ssh/ssh_host_rsa_key > /etc/ssh/ssh_host_rsa_key.pub | |
fi | |
if [ ! -f "/etc/ssh/ssh_host_ed25519_key.pub" ]; then | |
echo "- Generating host key: /etc/ssh/ssh_host_ed25519_key | |
ssh-keygen -y -f /etc/ssh/ssh_host_ed25519_key > /etc/ssh/ssh_host_ed25519_key.pub | |
fi | |
# Add keys to authorized_keys | |
if ! grep -q "${SSH_ROOT_KEY_PUB}" /root/.ssh/authorized_keys; then | |
echo " - Adding key for user: root to authorized_keys on remote server" | |
cat /root/.ssh/id_ed25519_${SSH_HOSTNAME}_root.pub >>/root/.ssh/authorized_keys | |
fi | |
if ! grep -q "${SSH_USER_KEY_PUB}" /home/${SSH_USER_NAME}/.ssh/authorized_keys; then | |
echo " - Adding key for user: ${SSH_USER_NAME} to authorized_keys on remote server" | |
cat /home/${SSH_USER_NAME}/.ssh/id_ed25519_${SSH_HOSTNAME}_${SSH_USER_NAME}.pub >>/home/${SSH_USER_NAME}/.ssh/authorized_keys | |
fi | |
cp /etc/ssh/sshd_config /etc/ssh/sshd_config.backup | |
cat <<'EOF' >/etc/ssh/sshd_config | |
HostKey /etc/ssh/ssh_host_rsa_key | |
HostKey /etc/ssh/ssh_host_ed25519_key | |
LogLevel VERBOSE | |
RekeyLimit 1G 1H | |
KexAlgorithms [email protected],diffie-hellman-group18-sha512,diffie-hellman-group16-sha512,diffie-hellman-group14-sha256 | |
Ciphers [email protected],[email protected],[email protected],aes256-ctr,aes192-ctr | |
MACs [email protected],[email protected],[email protected] | |
MaxAuthTries 5 | |
MaxSessions 5 | |
ClientAliveInterval 30 | |
ClientAliveCountMax 6 | |
TCPKeepAlive no | |
UsePAM yes | |
PasswordAuthentication no | |
ChallengeResponseAuthentication no | |
PubkeyAuthentication yes | |
AuthenticationMethods publickey | |
PermitRootLogin prohibit-password | |
PermitEmptyPasswords no | |
AllowAgentForwarding yes | |
AllowTcpForwarding no | |
X11Forwarding no | |
PrintMotd no | |
Compression no | |
#AcceptEnv LANG LC_* | |
# Subsystem sftp /usr/lib/ssh/sftp-server | |
# For Debian/Ubuntu | |
Subsystem sftp /usr/lib/openssh/sftp-server -f AUTHPRIV -l INFO | |
AuthorizedKeysFile .ssh/authorized_keys | |
#AuthorizedKeysFile /etc/ssh/authorized_keys/%u | |
#AllowUsers <your username> | |
#AllowGroups ssh-user | |
EOF | |
systemctl restart sshd || { | |
echo "Failed to restart SSH Daemon." | |
echo "Journal status:" | |
journalctl -xeu ssh.service | |
echo "Contents of SSH configuration file:" | |
cat /etc/ssh/sshd_config | |
exit 1 | |
} | |
exit 0 | |
ENDSSH | |
if [ $? -ne 0 ]; then | |
error "Failed to change SSH configuration on remote server." | |
exit 1 | |
fi |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment