Safely running a public-facing server's bash
SSH Access Protection#
SSH, as an important entry point for bash, is very dangerous when exposed directly to the public network on the default port 22. It can result in data loss and financial loss💔, or even become a tool for others to exploit😨. Therefore, how to securely access the bash of a cloud server has become a significant challenge⁉️.
Modify the port and login permissions. The default port 22 should be changed to a non-standard port, and password login should be disabled❌, using key-based🔑 authentication instead.
> vim /etc/ssh/sshd_config
Port non-standard port
PasswordAuthentication no
PubkeyAuthentication yes
Enable Firewall Protection#
In general, cloud platforms provide basic platform firewalls, combined with host firewalls, to resist certain intrusion attacks.
Firewall restrictions on source access. For users or companies with fixed public network🌏 IP addresses, restricting source access on the host firewall is sufficient. Here are the firewall settings for firewalld:
> firewall-cmd --zone=public --add-rich-rule='rule family="ipv4" source address="client IP" port protocol="tcp" port="SSH port" accept' --permanent
For ufw firewall settings:
> ufw allow from client IP to any port SSH port
For iptables firewall settings:
> iptables -A INPUT -p tcp --dport SSH port -s client IP -j ACCEPT
Restrict Access from Dynamic Public IP Addresses#
For users without fixed public IP addresses or dedicated lines, dynamic access restriction from source IP addresses can be achieved using DDNS and bash scripts. The principle is shown in the following diagram👇:
First, you need to purchase a domain name, which can be a free one. Common DDNS tools include Alibaba Cloud DDNS, Tencent DDNS, ddns-go, etc.
Next, write a script to obtain the dynamic IP address pointed to by the domain name (similar to DDNS, but can be considered as reverse DDNS). Taking ufw firewall as an example:
#!/bin/bash
# Initialize variables
current_time=$(date "+%Y-%m-%d %H:%M:%S")
DOMAIN="your domain"
DOMAIN_IP=$(nslookup $DOMAIN | awk '/^Address: / { print $2 }')
LOG_FILE="/var/log/ufw_update.log"
PORT="server's SSH port"
# Extract the last recorded IP from the log file
if [ -f "$LOG_FILE" ]; then
LAST_IP=$(grep "DOMAIN-IP:" $LOG_FILE | tail -1 | awk '{print $NF}')
else
LAST_IP=""
fi
# Update the log file
echo "$current_time: Current DOMAIN-IP: $DOMAIN_IP" >> $LOG_FILE
# Check if the IP has changed or the log file does not exist
if [ "$DOMAIN_IP" != "$LAST_IP" ] || [ -z "$LAST_IP" ]; then
echo "$current_time: IP address has changed, updating" >> $LOG_FILE
# Update UFW rules
# Delete all rules for the specified port to avoid duplication
ufw status numbered | grep " $PORT " | cut -d "[" -f2 | cut -d "]" -f1 | tac | while read -r line ; do
yes | ufw delete $line
done
# Add new rule
ufw allow from $DOMAIN_IP to any port $PORT
ufw deny $PORT
echo "$current_time: Update completed" >> $LOG_FILE
else
echo "$current_time: IP address has not changed, no need to update" >> $LOG_FILE
fi
# Print the current firewall status
ufw_status=$(ufw status)
echo "$current_time: Current firewall status:" >> $LOG_FILE
echo "$ufw_status" >> $LOG_FILE
echo "===============================" >> $LOG_FILE
Finally, set up a crontab schedule😄 to execute the script and obtain the IP address of the domain name.
End, celebration🎊.