Last active
January 7, 2025 16:02
-
-
Save lucabased/b2ea276aec5b1a0f8faad1b6c910a13c to your computer and use it in GitHub Desktop.
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 | |
| # | |
| # harden_lynis.sh | |
| # | |
| # Description: | |
| # This script automates several recommendations from a Lynis scan | |
| # on a Debian/Ubuntu-like system. It installs recommended packages, | |
| # modifies SSH and password policies, restricts some kernel modules, | |
| # and sets up basic banners. Many items require manual review or | |
| # additional configuration to be truly effective. | |
| # | |
| # Usage: | |
| # 1. Make executable: chmod +x harden_lynis.sh | |
| # 2. Run as root: sudo ./harden_lynis.sh | |
| # | |
| # DISCLAIMER: | |
| # - This is a DEMO script. Some changes may BREAK your system if | |
| # not reviewed or tested. Always BACK UP your system first. | |
| # - Partitioning changes, GRUB passwords, advanced MAC frameworks, | |
| # or rewriting file systems are not fully automated here. | |
| # - Adjust steps according to your environment, especially if | |
| # certain services or protocols are needed. | |
| set -euo pipefail | |
| #=== HELPER FUNCTIONS ================================================= | |
| info() { | |
| echo -e "\e[32m[INFO]\e[0m $*" | |
| } | |
| warning() { | |
| echo -e "\e[33m[WARN]\e[0m $*" | |
| } | |
| error() { | |
| echo -e "\e[31m[ERROR]\e[0m $*" >&2 | |
| } | |
| check_root() { | |
| if [[ $EUID -ne 0 ]]; then | |
| error "This script must be run as root." | |
| exit 1 | |
| fi | |
| } | |
| backup_file() { | |
| local file="$1" | |
| if [[ -f "$file" ]]; then | |
| cp -v "$file" "${file}.bak.$(date +%Y%m%d%H%M%S)" | |
| info "Created backup for $file" | |
| else | |
| warning "$file not found, skipping backup." | |
| fi | |
| } | |
| #=== 1) INSTALL RECOMMENDED PACKAGES ================================== | |
| install_packages() { | |
| info "Installing recommended packages per Lynis suggestions..." | |
| apt update | |
| apt -y install \ | |
| libpam-tmpdir \ | |
| apt-listbugs \ | |
| needrestart \ | |
| debsums \ | |
| apt-show-versions \ | |
| libpam-pwquality \ | |
| sysstat \ | |
| acct | |
| # Explanation: | |
| # - libpam-tmpdir: sets $TMP/$TMPDIR for each user session | |
| # - apt-listbugs: see critical bugs before installing/upgrading | |
| # - needrestart: check which daemons need a restart after updates | |
| # - debsums: verify installed package files against checksums | |
| # - apt-show-versions: list available package versions | |
| # - pam-cracklib: enforce stronger password policy | |
| # - sysstat: collects accounting data (CPU, I/O usage, etc.) | |
| # - acct: enables process accounting | |
| # Initialize or enable some of these if needed | |
| info "Enabling sysstat..." | |
| sed -i 's/ENABLED="false"/ENABLED="true"/' /etc/default/sysstat || true | |
| systemctl enable sysstat | |
| systemctl start sysstat | |
| info "Enabling process accounting (acct)..." | |
| systemctl enable acct | |
| systemctl start acct | |
| } | |
| #=== 2) PASSWORD & AUTH HARDENING ===================================== | |
| harden_auth() { | |
| info "Hardening password and authentication settings..." | |
| # Install libpam-pwquality | |
| apt update | |
| apt install -y libpam-pwquality | |
| # Configure /etc/security/pwquality.conf | |
| local pwquality_conf="/etc/security/pwquality.conf" | |
| backup_file "$pwquality_conf" | |
| cat <<EOF >"$pwquality_conf" | |
| minlen = 12 | |
| minclass = 3 | |
| difok = 4 | |
| dictcheck = 1 | |
| maxrepeat = 3 | |
| EOF | |
| # Configure PAM in /etc/pam.d/common-password | |
| local pam_common_pwd="/etc/pam.d/common-password" | |
| backup_file "$pam_common_pwd" | |
| sed -i \ | |
| 's/^\s*password\s\+requisite\s\+pam_cracklib.so.*/# Removed pam_cracklib/' \ | |
| "$pam_common_pwd" | |
| echo "password requisite pam_pwquality.so retry=3 minlen=12 difok=4 ucredit=-1 lcredit=-1 dcredit=-1 ocredit=-1 enforce_for_root" >> "$pam_common_pwd" | |
| # Enforce password aging policies | |
| local login_defs="/etc/login.defs" | |
| backup_file "$login_defs" | |
| sed -i 's/^\s*PASS_MIN_DAYS.*/PASS_MIN_DAYS 1/' "$login_defs" | |
| sed -i 's/^\s*PASS_MAX_DAYS.*/PASS_MAX_DAYS 90/' "$login_defs" | |
| sed -i 's/^\s*PASS_WARN_AGE.*/PASS_WARN_AGE 7/' "$login_defs" | |
| info "Password and authentication hardening completed." | |
| } | |
| #=== 3) SSH HARDENING ================================================= | |
| harden_ssh() { | |
| info "Hardening SSH configuration..." | |
| local ssh_config="/etc/ssh/sshd_config" | |
| backup_file "$ssh_config" | |
| # Example changes from Lynis: | |
| # - Change SSH port from 22 -> e.g. 2222 (adjust as desired) | |
| # - Reduce MaxAuthTries from 6 to 3 | |
| # - Reduce MaxSessions from 10 to 2 | |
| # - Disable compression | |
| # - Set LogLevel to VERBOSE | |
| # - Disallow AgentForwarding, TCPForwarding, X11Forwarding, etc. | |
| sed -i 's/^#\?Port .*/Port 2222/' "$ssh_config" | |
| sed -i 's/^#\?MaxAuthTries .*/MaxAuthTries 3/' "$ssh_config" | |
| sed -i 's/^#\?MaxSessions .*/MaxSessions 2/' "$ssh_config" | |
| sed -i 's/^#\?Compression .*/Compression no/' "$ssh_config" | |
| sed -i 's/^#\?AllowTcpForwarding .*/AllowTcpForwarding no/' "$ssh_config" | |
| sed -i 's/^#\?AllowAgentForwarding .*/AllowAgentForwarding no/' "$ssh_config" | |
| sed -i 's/^#\?ClientAliveCountMax .*/ClientAliveCountMax 2/' "$ssh_config" | |
| sed -i 's/^#\?TCPKeepAlive .*/TCPKeepAlive no/' "$ssh_config" | |
| sed -i 's/^#\?LogLevel .*/LogLevel VERBOSE/' "$ssh_config" | |
| # If you do rely on forwarding or other features, skip changing them to 'no'. | |
| # Make sure the new port is open in your firewall: | |
| ufw allow 2222/tcp || true | |
| systemctl restart sshd | |
| info "SSH port changed to 2222 (if that line wasn't commented out)." | |
| } | |
| #=== 4) GRUB PASSWORD (MANUAL STEP RECOMMENDED) ======================= | |
| set_grub_password() { | |
| # Automating GRUB password can be tricky in a script. Typically: | |
| # 1. Generate hashed password: `grub-mkpasswd-pbkdf2` | |
| # 2. Add config to /boot/grub/grub.cfg or /etc/grub.d/40_custom | |
| # We'll just demonstrate: | |
| warning "Setting a GRUB password is recommended but can break headless servers." | |
| warning "Manual step: run 'grub-mkpasswd-pbkdf2' then add it to /boot/grub/grub.cfg." | |
| # Example lines might be: | |
| # cat <<EOF >> /etc/grub.d/40_custom | |
| # set superusers="root" | |
| # password_pbkdf2 root grub.pbkdf2.sha512.10000... | |
| # EOF | |
| } | |
| #=== 5) MISC HARDENING STEPS ========================================== | |
| misc_hardening() { | |
| # 5a) Restrict core dumps in /etc/security/limits.conf | |
| local limits_conf="/etc/security/limits.conf" | |
| backup_file "$limits_conf" | |
| if ! grep -q "hard core 0" "$limits_conf"; then | |
| echo "* hard core 0" >> "$limits_conf" | |
| fi | |
| # 5b) Set legal banners /etc/issue and /etc/issue.net | |
| # WARNING: Some systems show these on login attempts. | |
| echo "Authorized uses only. All activity may be monitored and reported." > /etc/issue | |
| echo "Authorized uses only. All activity may be monitored and reported." > /etc/issue.net | |
| # 5c) Enable process accounting (already done in install_packages) | |
| # 5d) Enable sysstat data collection (already done in install_packages) | |
| # 5e) AIDE config: Use stronger checksums (sha512). Typically in /etc/aide/aide.conf | |
| local aide_conf="/etc/aide/aide.conf" | |
| if [[ -f "$aide_conf" ]]; then | |
| backup_file "$aide_conf" | |
| # Example of forcing AIDE to use stronger checksums | |
| sed -i 's/^Checksums = .*/Checksums = sha512/' "$aide_conf" || true | |
| fi | |
| # 5f) Possibly disable unneeded protocols: dccp, sctp, rds, tipc | |
| # You can blacklist them in /etc/modprobe.d/blacklist.conf | |
| local blacklist_conf="/etc/modprobe.d/blacklist-lynis.conf" | |
| { | |
| echo "blacklist dccp" | |
| echo "blacklist sctp" | |
| echo "blacklist rds" | |
| echo "blacklist tipc" | |
| } >> "$blacklist_conf" | |
| info "Blacklisted protocols dccp, sctp, rds, tipc (if not needed)." | |
| # 5g) Purge old packages | |
| # We'll do a quick apt autoremove/clean. For a thorough purge, you'd do: | |
| info "Purging old/removed packages..." | |
| apt -y autoremove | |
| apt -y autoclean | |
| # If you know the package names that Lynis identified, do `dpkg --purge <pkg>`. | |
| # 5h) Add your server's FQDN to /etc/hosts if missing | |
| # Example FQDN: myserver.example.com | |
| # Example IP: 192.168.1.10 | |
| # Adjust to your actual IP and hostname | |
| # echo "192.168.1.10 myserver.example.com myserver" >> /etc/hosts | |
| warning "Remember to add your system's IP, FQDN, and hostname to /etc/hosts if needed." | |
| # 5i) Check or remove unneeded iptables rules (manual step) | |
| # For a thorough approach, run: iptables -L --line-numbers | |
| # 5j) Restrict file permissions | |
| # This step can be large; here's a minimal example: | |
| find / -xdev -type f -perm -0002 -exec chmod o-w {} \; 2>/dev/null || true | |
| # More advanced checks require manual review of each path. | |
| # 5k) Home directories check | |
| # This sets 700 on all home directories: | |
| for d in /home/*; do | |
| if [ -d "$d" ]; then | |
| chmod 700 "$d" || true | |
| fi | |
| done | |
| info "Misc hardening steps completed. Some require manual verification." | |
| } | |
| #=== PRIVACY ========================================================== | |
| randomize_hostname() { | |
| info "Randomizing hostname..." | |
| # Generate a random hostname (8 characters: letters and digits) | |
| local new_hostname | |
| new_hostname=$(tr -dc 'a-zA-Z0-9' < /dev/urandom | head -c 8) | |
| # Optionally, use a wordlist instead of random characters | |
| # Uncomment the following lines if you prefer word-based hostnames: | |
| # local wordlist=("alpha" "bravo" "charlie" "delta" "echo" "foxtrot" "golf" "hotel") | |
| # new_hostname="${wordlist[$RANDOM % ${#wordlist[@]}]}-$RANDOM" | |
| info "Generated random hostname: $new_hostname" | |
| # Backup the current hostname | |
| local current_hostname | |
| current_hostname=$(hostname) | |
| echo "$current_hostname" > /etc/hostname.bak | |
| info "Current hostname backed up to /etc/hostname.bak" | |
| # Set the new hostname (temporary and persistent) | |
| hostnamectl set-hostname "$new_hostname" | |
| # Update /etc/hosts for the new hostname | |
| if grep -q "$current_hostname" /etc/hosts; then | |
| sed -i "s/$current_hostname/$new_hostname/g" /etc/hosts | |
| else | |
| echo "127.0.0.1 $new_hostname" >> /etc/hosts | |
| fi | |
| info "Hostname changed to $new_hostname" | |
| } | |
| #=== MAIN EXECUTION =================================================== | |
| main() { | |
| check_root | |
| install_packages | |
| harden_auth | |
| harden_ssh | |
| # set_grub_password | |
| misc_hardening | |
| randomize_hostname | |
| info "All steps completed. Review any warnings above, then reboot if needed." | |
| } | |
| main "$@" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment