Skip to content

Instantly share code, notes, and snippets.

@loopyd
Last active July 14, 2024 22:27
Show Gist options
  • Save loopyd/b9136aa2837d33131a96b0ff4217e2f5 to your computer and use it in GitHub Desktop.
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
#!/usr/bin/env bash
# config_ssh.sh - A script to set up your VPS SSH configuration
# Author: loopyd <loopyd@github.com> 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 curve25519-sha256@libssh.org,diffie-hellman-group18-sha512,diffie-hellman-group16-sha512,diffie-hellman-group14-sha256
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com
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