Safely running a public-facing server's bash
Protection for SSH Access#
Exposing SSH directly to the public network on the default port 22 is a highly dangerous practice. It can result in data loss and financial damages💔, or even turn the server into a tool for malicious activities😨. Therefore, securely accessing a cloud server's bash has become a significant challenge⁉️.
Changing the Port and Login Permissions
The default port 22 should be changed to a non-standard port, and password login should be disabled❌ in favor of key-based authentication🔑.
> vim /etc/ssh/sshd_config
Port Non-standard Port
PasswordAuthentication no
PubkeyAuthentication yes
Enabling Firewall Protection
In general, cloud platforms provide basic platform firewalls, which, combined with host firewalls, can withstand certain intrusion attacks.
Firewall restrictions on source access
For users or companies with fixed public🌏 IP addresses, restricting source access at the host firewall level 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:
> ufw allow from Client IP to any port SSH Port
For iptables firewall:
> iptables -A INPUT -p tcp --dport SSH Port -s Client IP -j ACCEPT
Limiting Access from Dynamic Public IPs
For users without fixed public IPs or dedicated lines, dynamic access restrictions can be implemented using DDNS and bash scripts. The principle is shown in the following diagram👇:
First, 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 in reverse). 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 duplicates
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 update needed" >> $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 resolved by the domain name.
That's it, celebration time! 🎊