Bash Script
Linux Hardening
Welcome
This script is designed to automate the hardening process of an operating system and Apache web server. It includes options for interactive use and fully automated execution, providing flexibility for system administrators.
It performs various security enhancements by updating and upgrading system packages, installing essential security tools, configuring firewalls, securing SSH, disabling unnecessary services, and setting up Fail2Ban, Auditd, and automatic updates. The script also includes Apache-specific hardening tasks such as securing configuration files, disabling unnecessary modules, and setting up log rotation. The script is versatile, supporting multiple Linux distributions (Ubuntu, Debian, RHEL, Rocky Linux) and can run interactively or automatically with the -a flag for full automation. It ensures the script is run as root, logs all actions, detects the operating system, and adapts its actions accordingly. This tool provides a robust, automated approach to enhancing server security, catering to both general system and Apache-specific hardening requirements.
* Please be aware, the coding on this site is meant to guide. Testing these scripts in a non-production environment is strongly encouraged. I take no responsibility for use of any scripts on this site or on my github repositories.
Script:
#!/bin/bash
# In no event shall the author or copyright holder be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise..
# The software is provided 'as is', without warranty of any kind, express or implied
# This was developed by Jonathan Wilson, 14-JUN-2024
#
# Flags : -u username -a
# -u for username ( does not create account, will need to be existing, but script will add to sudo / wheel group if executed with -a or requested )
# -a will flag the script to execute operating system hardening with all options bypassed and set to execute
# Both flags are optional
#
LOGFILE="/var/tmp/harden.txt"
USERNAME=""
ALL_OPTIONS=false
# Ensure the script is run as root
if [ "$(id -u)" -ne 0 ]; then
echo "This script must be run as root. Use sudo."
exit 1
fi
# Log function to record script execution steps
log() {
echo "$(date +'%Y-%m-%d %H:%M:%S') - $1" | tee -a $LOGFILE
}
# Detect the operating system
if [ -f /etc/os-release ]; then
. /etc/os-release
OS=$ID
VERSION=$VERSION_ID
log "Operating System: $OS $VERSION"
else
log "Cannot determine the operating system. Exiting."
exit 1
fi
# Determine the correct SSH service name
if systemctl list-units --full -all | grep -Fq 'ssh.service'; then
SSH_SERVICE="ssh"
else
SSH_SERVICE="sshd"
fi
# Function to display usage information
usage() {
echo "Usage: $0 [-u username] [-a]"
exit 1
}
# Parse command-line arguments
while getopts ":u:a" opt; do
case $opt in
u)
USERNAME=$OPTARG
;;
a)
ALL_OPTIONS=true
;;
\?)
echo "Invalid option: -$OPTARG" >&2
usage
;;
:)
echo "Option -$OPTARG requires an argument." >&2
usage
;;
esac
done
# Function to display the main menu and get the user's choice
main_menu() {
echo "Please choose an option:"
echo "1. System Hardening"
echo "2. Apache Hardening"
echo "3. Choose Specific Section"
echo "4. Exit"
read -p "Enter your choice (1, 2, 3 or 4): " main_choice
case $main_choice in
1)
log "Selected System Hardening"
system_hardening
;;
2)
log "Selected Apache Hardening"
apache_hardening
;;
3)
log "Selected Specific Section"
specific_section_menu
;;
4)
log "Exiting"
exit 0
;;
*)
log "Invalid choice, exiting"
exit 1
;;
esac
}
# Function to display the specific section menu and get the user's choice
specific_section_menu() {
while true; do
echo "Please choose a section to harden:"
echo "1. Update and Upgrade System Packages"
echo "2. Install Essential Security Packages"
echo "3. Configure Firewall"
echo "4. Secure SSH"
echo "5. Disable Unnecessary Services"
echo "6. Setup Fail2Ban"
echo "7. Setup Auditd"
echo "8. Setup Automatic Updates"
echo "9. Harden Kernel Parameters"
echo "10. Secure Apache Configuration"
echo "11. Disable Unnecessary Apache Modules"
echo "12. Setup Apache Log Rotation"
echo "13. Exit"
read -p "Enter your choice (1-13): " section_choice
case $section_choice in
1)
log "Selected Update and Upgrade System Packages"
update_and_upgrade
;;
2)
log "Selected Install Essential Security Packages"
install_security_packages
;;
3)
log "Selected Configure Firewall"
configure_firewall
;;
4)
log "Selected Secure SSH"
secure_ssh
;;
5)
log "Selected Disable Unnecessary Services"
disable_unnecessary_services
;;
6)
log "Selected Setup Fail2Ban"
setup_fail2ban
;;
7)
log "Selected Setup Auditd"
setup_auditd
;;
8)
log "Selected Setup Automatic Updates"
setup_unattended_upgrades
;;
9)
log "Selected Harden Kernel Parameters"
harden_kernel_parameters
;;
10)
log "Selected Secure Apache Configuration"
secure_apache_config
;;
11)
log "Selected Disable Unnecessary Apache Modules"
disable_unnecessary_apache_modules
;;
12)
log "Selected Setup Apache Log Rotation"
setup_apache_log_rotation
;;
13)
log "Exiting Specific Section Menu"
break
;;
*)
log "Invalid choice, please try again"
;;
esac
done
}
# Function for system hardening
system_hardening() {
update_and_upgrade
install_security_packages
configure_firewall
secure_ssh
disable_unnecessary_services
setup_fail2ban
setup_auditd
setup_unattended_upgrades
harden_kernel_parameters
log "System hardening completed. Please review the configuration and make any additional changes as needed."
}
# Function for updating and upgrading system packages
update_and_upgrade() {
if $ALL_OPTIONS || ask_user "Updating and upgrading system packages"; then
case $OS in
ubuntu|debian)
apt-get update && apt-get upgrade -y | tee -a $LOGFILE
;;
rhel|rocky)
yum update -y | tee -a $LOGFILE
;;
*)
log "Unsupported OS. Exiting."
exit 1
;;
esac
fi
}
# Function to install essential security packages
install_security_packages() {
if $ALL_OPTIONS || ask_user "Installing essential security packages"; then
case $OS in
ubuntu|debian)
apt-get install -y ufw fail2ban auditd | tee -a $LOGFILE
;;
rhel|rocky)
yum install -y epel-release | tee -a $LOGFILE
yum install -y firewalld fail2ban audit dnf-automatic | tee -a $LOGFILE
;;
*)
log "Unsupported OS. Exiting."
exit 1
;;
esac
fi
}
# Function to configure the firewall
configure_firewall() {
if $ALL_OPTIONS || ask_user "Configuring the firewall"; then
case $OS in
ubuntu|debian)
yes | ufw default deny incoming | tee -a $LOGFILE
yes | ufw default allow outgoing | tee -a $LOGFILE
yes | ufw allow ssh | tee -a $LOGFILE
yes | ufw enable | tee -a $LOGFILE
;;
rhel|rocky)
systemctl start firewalld | tee -a $LOGFILE
systemctl enable firewalld | tee -a $LOGFILE
firewall-cmd --permanent --zone=drop --set-target=DROP | tee -a $LOGFILE
firewall-cmd --permanent --zone=drop --add-service=ssh | tee -a $LOGFILE
firewall-cmd --reload | tee -a $LOGFILE
;;
*)
log "Unsupported OS. Exiting."
exit 1
;;
esac
fi
}
# Function to add user to sudo or wheel group
add_user_to_sudo() {
if [ -z "$USERNAME" ]; then
read -p "Enter the username you want to add to the sudo/wheel group: " USERNAME
fi
if id "$USERNAME" &>/dev/null; then
case $OS in
ubuntu|debian)
usermod -aG sudo "$USERNAME"
log "Added $USERNAME to sudo group."
;;
rhel|rocky)
usermod -aG wheel "$USERNAME"
log "Added $USERNAME to wheel group."
;;
*)
log "Unsupported OS. Exiting."
exit 1
;;
esac
else
log "User $USERNAME does not exist. Exiting."
exit 1
fi
}
# Function to secure SSH
secure_ssh() {
if $ALL_OPTIONS || ask_user "Disabling root login and securing SSH"; then
add_user_to_sudo
sed -i 's/PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config
sed -i 's/#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
systemctl restart $SSH_SERVICE | tee -a $LOGFILE
fi
}
# Function to disable unnecessary services
disable_unnecessary_services() {
if $ALL_OPTIONS || ask_user "Disabling unnecessary services"; then
case $OS in
ubuntu|debian)
systemctl disable avahi-daemon | tee -a $LOGFILE
systemctl disable cups | tee -a $LOGFILE
systemctl disable bluetooth | tee -a $LOGFILE
;;
rhel|rocky)
systemctl disable avahi-daemon | tee -a $LOGFILE
systemctl disable cups | tee -a $LOGFILE
systemctl disable bluetooth | tee -a $LOGFILE
;;
*)
log "Unsupported OS. Exiting."
exit 1
;;
esac
fi
}
# Function to set up Fail2Ban
setup_fail2ban() {
if $ALL_OPTIONS || ask_user "Setting up fail2ban"; then
case $OS in
ubuntu|debian)
apt-get install -y fail2ban | tee -a $LOGFILE
;;
rhel|rocky)
yum install -y fail2ban | tee -a $LOGFILE
;;
*)
log "Unsupported OS. Exiting."
exit 1
;;
esac
# Ensure the Fail2Ban service file exists
FAIL2BAN_SERVER=$(which fail2ban-server)
if [ ! -f "$FAIL2BAN_SERVER" ]; then
log "Fail2Ban server executable not found. Exiting."
exit 1
fi
# Create necessary directories and set correct permissions
mkdir -p /etc/fail2ban
mkdir -p /var/run/fail2ban
chown -R root:root /var/run/fail2ban
chmod -R 755 /var/run/fail2ban
cat <<EOF > /etc/fail2ban/jail.local
[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 3
destemail = root@localhost
sendername = Fail2Ban
mta = sendmail
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
EOF
systemctl enable fail2ban | tee -a $LOGFILE
systemctl start fail2ban | tee -a $LOGFILE
# Verify Fail2Ban status
if systemctl is-active --quiet fail2ban; then
log "Fail2Ban started successfully."
else
log "Fail2Ban failed to start."
systemctl status fail2ban | tee -a $LOGFILE
fi
fi
}
# Function to set up Auditd
setup_auditd() {
if $ALL_OPTIONS || ask_user "Setting up auditd"; then
cat <<EOF > /etc/audit/audit.rules
-w /etc/passwd -p wa -k passwd_changes
-w /etc/shadow -p wa -k shadow_changes
-w /etc/group -p wa -k group_changes
EOF
systemctl enable auditd | tee -a $LOGFILE
systemctl start auditd | tee -a $LOGFILE
fi
}
# Function to set up automatic updates
setup_unattended_upgrades() {
if $ALL_OPTIONS || ask_user "Setting up automatic updates"; then
case $OS in
ubuntu|debian)
echo "unattended-upgrades unattended-upgrades/enable_auto_updates boolean true" | debconf-set-selections
apt-get install -y unattended-upgrades | tee -a $LOGFILE
dpkg-reconfigure -f noninteractive unattended-upgrades | tee -a $LOGFILE
;;
rhel|rocky)
log "Configuring automatic updates with dnf-automatic..."
cat <<EOF > /etc/dnf/automatic.conf
[commands]
upgrade_type = security
random_sleep = 360
[emitters]
emit_via = motd
[email]
email_from = root@localhost
email_to = root
email_host = localhost
[base]
debuglevel = 1
# skip_broken = True
# mdpolicy = group:main
# assumeyes = True
[main]
enabled = True
EOF
systemctl enable --now dnf-automatic.timer | tee -a $LOGFILE
;;
*)
log "Unsupported OS. Exiting."
exit 1
;;
esac
fi
}
# Function to harden kernel parameters
harden_kernel_parameters() {
if $ALL_OPTIONS || ask_user "Hardening kernel parameters"; then
cat <<EOF > /etc/sysctl.d/99-sysctl.conf
# IP Spoofing protection
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
# Ignore ICMP broadcast requests
net.ipv4.icmp_echo_ignore_broadcasts = 1
# Disable source packet routing
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.default.accept_source_route = 0
# Ignore send redirects
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0
# Block SYN attacks
net.ipv4.tcp_syncookies = 1
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv4.conf.all.secure_redirects = 1
net.ipv4.conf.default.secure_redirects = 1
EOF
sysctl -p /etc/sysctl.d/99-sysctl.conf | tee -a $LOGFILE
fi
}
# Function to perform Apache hardening
apache_hardening() {
log "Starting Apache hardening process..."
verify_apache_installed
secure_apache_config
disable_unnecessary_apache_modules
setup_apache_log_rotation
log "Apache hardening completed. Please review the configuration and make any additional changes as needed."
}
# Function to verify if Apache is installed
verify_apache_installed() {
if [ "$(which apache2 2>/dev/null)" ] || [ "$(which httpd 2>/dev/null)" ]; then
log "Apache is installed."
else
log "Apache is not installed. Exiting."
exit 1
fi
}
# Function to secure Apache configuration
secure_apache_config() {
if ask_user "Securing Apache configuration"; then
APACHE_CONF="/etc/apache2/apache2.conf"
if [ -f "/etc/httpd/conf/httpd.conf" ]; then
APACHE_CONF="/etc/httpd/conf/httpd.conf"
fi
cat <<EOF >> $APACHE_CONF
# Disable directory listing
<Directory /var/www/>
Options -Indexes
</Directory>
# Disable server signature
ServerSignature Off
ServerTokens Prod
# Disable TRACE HTTP method
TraceEnable Off
EOF
systemctl restart apache2 2>/dev/null || systemctl restart httpd
log "Apache configuration secured."
fi
}
# Function to disable unnecessary Apache modules
disable_unnecessary_apache_modules() {
if ask_user "Disabling unnecessary Apache modules"; then
a2dismod autoindex 2>/dev/null || echo "autoindex module not found" | tee -a $LOGFILE
a2dismod status 2>/dev/null || echo "status module not found" | tee -a $LOGFILE
systemctl restart apache2 2>/dev/null || systemctl restart httpd
log "Unnecessary Apache modules disabled."
fi
}
# Function to set up Apache log rotation
setup_apache_log_rotation() {
if ask_user "Setting up Apache log rotation"; then
LOGROTATE_CONF="/etc/logrotate.d/apache2"
if [ -f "/etc/logrotate.d/httpd" ]; then
LOGROTATE_CONF="/etc/logrotate.d/httpd"
fi
cat <<EOF > $LOGROTATE_CONF
/var/log/apache2/*.log {
weekly
missingok
rotate 52
compress
delaycompress
notifempty
create 640 root adm
sharedscripts
postrotate
/etc/init.d/apache2 reload > /dev/null
endscript
}
EOF
log "Apache log rotation setup completed."
fi
}
# Function to prompt the user for choices for each hardening task
ask_user() {
log "Please choose an option for $1:"
echo "1. Enable"
echo "2. Leave alone"
read -p "Enter your choice (1 or 2): " choice
case $choice in
1)
log "Enabled $1"
return 0
;;
2)
log "Left $1 alone"
return 1
;;
*)
log "Invalid choice, leaving $1 alone"
return 1
;;
esac
}
# Start the main script
log "Starting hardening script..."
# Display the main menu and proceed based on the user's choice
if $ALL_OPTIONS; then
log "All options enabled for system hardening."
system_hardening
else
main_menu
fi
# End the script
log "Script execution completed. Please check the log file at $LOGFILE for details."