|
1 | 1 | #!/bin/bash |
2 | 2 |
|
3 | 3 | # Debian and Ubuntu Server Hardening Interactive Script |
4 | | -# Version: 0.78.5 | 2025-12-31 |
| 4 | +# Version: 0.79.0 | 2026-01-13 |
5 | 5 | # Changelog: |
| 6 | +# - v0.79.0: Added CrowdSec, now you can choose between fail2ban and CrowdSec for system level firewall. |
6 | 7 | # - v0.78.5: Switched to using nano as the default editor in .bashrc. |
7 | 8 | # - v0.78.4: Improved configure_swap to detect swap partitions vs files. |
8 | 9 | # Prevents 'fallocate' crashes on physical partitions by offering to disable them or skip. |
9 | 10 | # - v0.78.3: Update the summary to try to show the right environment detection based on finding personal VMs and cloud VPS. |
10 | 11 | # Run update & upgrade in the final step to ensure system is fully updated after restart. |
11 | | -# - v0.78.2: In configure_system set choosen hostname from collect_config in the /etc/hosts |
| 12 | +# - v0.78.2: In configure_system set chosen hostname from collect_config in the /etc/hosts |
12 | 13 | # - v0.78.1: Collect config failure fixed on IPv6 only VPS. |
13 | 14 | # - v0.78: Script tries to handles different environments: Direct Public IP, NAT/Router and Local VM only |
14 | 15 | # The configure_ssh function provides context-aware instructions based on different environments. |
|
34 | 35 | # - v0.68: Enable UFW IPv6 support if available |
35 | 36 | # - v0.67: Do not log taiscale auth key in log file |
36 | 37 | # - v0.66: While configuring and in the summary, display both IPv6 and IPv4. |
37 | | -# - v0.65: If reconfigure locales - appy newly configured locale to the current environment. |
| 38 | +# - v0.65: If reconfigure locales - apply newly configured locale to the current environment. |
38 | 39 | # - v0.64: Tested at Debian 13 to confirm it works as expected |
39 | 40 | # - v0.63: Added ssh install in key packages |
40 | 41 | # - v0.62: Added fix for fail2ban by creating empty ufw log file |
|
96 | 97 | set -euo pipefail |
97 | 98 |
|
98 | 99 | # --- Update Configuration --- |
99 | | -CURRENT_VERSION="0.78.5" |
| 100 | +CURRENT_VERSION="0.79.0" |
100 | 101 | SCRIPT_URL="https://raw.githubusercontent.com/buildplan/du_setup/refs/heads/main/du_setup.sh" |
101 | 102 | CHECKSUM_URL="${SCRIPT_URL}.sha256" |
102 | 103 |
|
@@ -155,6 +156,8 @@ ID="" # This will be populated from /etc/os-release |
155 | 156 | FAILED_SERVICES=() |
156 | 157 | PREVIOUS_SSH_PORT="" |
157 | 158 |
|
| 159 | +IDS_INSTALLED="" |
| 160 | + |
158 | 161 | # --- --help --- |
159 | 162 | show_usage() { |
160 | 163 | printf "\n" |
@@ -252,7 +255,7 @@ print_header() { |
252 | 255 | printf '%s\n' "${CYAN}╔═════════════════════════════════════════════════════════════════╗${NC}" |
253 | 256 | printf '%s\n' "${CYAN}║ ║${NC}" |
254 | 257 | printf '%s\n' "${CYAN}║ DEBIAN/UBUNTU SERVER SETUP AND HARDENING SCRIPT ║${NC}" |
255 | | - printf '%s\n' "${CYAN}║ v0.78.5 | 2025-12-31 ║${NC}" |
| 258 | + printf '%s\n' "${CYAN}║ v0.79.0 | 2026-01-13 ║${NC}" |
256 | 259 | printf '%s\n' "${CYAN}║ ║${NC}" |
257 | 260 | printf '%s\n' "${CYAN}╚═════════════════════════════════════════════════════════════════╝${NC}" |
258 | 261 | printf '\n' |
@@ -2912,10 +2915,10 @@ install_packages() { |
2912 | 2915 | fi |
2913 | 2916 | print_info "Installing essential packages..." |
2914 | 2917 | if ! apt-get install -y -qq \ |
2915 | | - ufw fail2ban unattended-upgrades chrony \ |
2916 | | - rsync wget vim htop iotop nethogs netcat-traditional ncdu \ |
| 2918 | + ufw unattended-upgrades chrony rsync wget \ |
| 2919 | + vim htop iotop nethogs netcat-traditional ncdu \ |
2917 | 2920 | tree rsyslog cron jq gawk coreutils perl skopeo git \ |
2918 | | - apt-listchanges ca-certificates gnupg logrotate \ |
| 2921 | + apt-listchanges ca-certificates gnupg logrotate make \ |
2919 | 2922 | ssh openssh-client openssh-server; then |
2920 | 2923 | print_error "Failed to install one or more essential packages." |
2921 | 2924 | exit 1 |
@@ -3801,6 +3804,15 @@ configure_firewall() { |
3801 | 3804 | configure_fail2ban() { |
3802 | 3805 | print_section "Fail2Ban Configuration" |
3803 | 3806 |
|
| 3807 | + # Install Fail2Ban if not present |
| 3808 | + if ! dpkg -l fail2ban | grep -q ^ii; then |
| 3809 | + print_info "Installing Fail2Ban..." |
| 3810 | + if ! apt-get install -y -qq fail2ban; then |
| 3811 | + print_error "Failed to install Fail2Ban." |
| 3812 | + return 1 |
| 3813 | + fi |
| 3814 | + fi |
| 3815 | + |
3804 | 3816 | # --- Collect User IPs to Ignore --- |
3805 | 3817 | local -a IGNORE_IPS=("127.0.0.1/8" "::1") # Array for easier dedup. |
3806 | 3818 | local -a INVALID_IPS=() |
@@ -3987,6 +3999,92 @@ EOF |
3987 | 3999 | log "Fail2Ban configuration completed." |
3988 | 4000 | } |
3989 | 4001 |
|
| 4002 | +configure_crowdsec() { |
| 4003 | + print_section "CrowdSec Configuration" |
| 4004 | + |
| 4005 | + # Check if already installed |
| 4006 | + if command -v crowdsec >/dev/null 2>&1; then |
| 4007 | + print_info "CrowdSec is already installed." |
| 4008 | + else |
| 4009 | + print_info "Setting up CrowdSec repository..." |
| 4010 | + if ! curl -s https://install.crowdsec.net | sh >> "$LOG_FILE" 2>&1; then |
| 4011 | + print_error "Failed to setup CrowdSec repository." |
| 4012 | + return 1 |
| 4013 | + fi |
| 4014 | + |
| 4015 | + print_info "Installing CrowdSec agent..." |
| 4016 | + if ! apt-get update -qq || ! apt-get install -y -qq crowdsec; then |
| 4017 | + print_error "Failed to install CrowdSec." |
| 4018 | + return 1 |
| 4019 | + fi |
| 4020 | + print_success "CrowdSec agent installed." |
| 4021 | + fi |
| 4022 | + |
| 4023 | + # Install Firewall Bouncer |
| 4024 | + if ! dpkg -l crowdsec-firewall-bouncer-iptables | grep -q ^ii; then |
| 4025 | + print_info "Installing CrowdSec Firewall Bouncer (iptables/UFW support)..." |
| 4026 | + if ! apt-get install -y -qq crowdsec-firewall-bouncer-iptables; then |
| 4027 | + print_warning "Failed to install firewall bouncer. CrowdSec will detect but NOT block attacks." |
| 4028 | + else |
| 4029 | + print_success "CrowdSec Firewall Bouncer installed." |
| 4030 | + fi |
| 4031 | + else |
| 4032 | + print_info "CrowdSec Firewall Bouncer already installed." |
| 4033 | + fi |
| 4034 | + # UFW Log Acquisition (Parity with Fail2Ban) |
| 4035 | + if [[ -d /etc/crowdsec/acquis.d ]]; then |
| 4036 | + print_info "Configuring UFW log acquisition..." |
| 4037 | + if [[ ! -f /var/log/ufw.log ]]; then |
| 4038 | + touch /var/log/ufw.log |
| 4039 | + print_info "Created empty /var/log/ufw.log for monitoring." |
| 4040 | + fi |
| 4041 | + cat <<EOF > /etc/crowdsec/acquis.d/ufw.yaml |
| 4042 | +filenames: |
| 4043 | + - /var/log/ufw.log |
| 4044 | +labels: |
| 4045 | + type: syslog |
| 4046 | +EOF |
| 4047 | + print_success "Added /var/log/ufw.log to CrowdSec acquisition." |
| 4048 | + fi |
| 4049 | + |
| 4050 | + # Enrollment |
| 4051 | + if confirm "Enroll this instance in the CrowdSec Console (optional)?" "n"; then |
| 4052 | + local ENROLL_KEY |
| 4053 | + while true; do |
| 4054 | + read -rp "$(printf '%s' "${CYAN}Enter your CrowdSec Enrollment Key: ${NC}")" ENROLL_KEY |
| 4055 | + if [[ -n "$ENROLL_KEY" ]]; then |
| 4056 | + print_info "Enrolling instance..." |
| 4057 | + if cscli console enroll "$ENROLL_KEY" 2>&1 | tee -a "$LOG_FILE"; then |
| 4058 | + print_success "Instance enrolled successfully." |
| 4059 | + break |
| 4060 | + else |
| 4061 | + print_error "Enrollment failed. Check the key and try again." |
| 4062 | + if confirm "Skip enrollment?" "n"; then break; fi |
| 4063 | + fi |
| 4064 | + else |
| 4065 | + print_error "Key cannot be empty." |
| 4066 | + fi |
| 4067 | + done |
| 4068 | + fi |
| 4069 | + |
| 4070 | + # Reload to ensure everything is active |
| 4071 | + systemctl restart crowdsec |
| 4072 | + print_success "CrowdSec configuration completed." |
| 4073 | + |
| 4074 | + # Help Section |
| 4075 | + printf '\n%s\n' "${YELLOW}CrowdSec Quick Reference:${NC}" |
| 4076 | + printf " %-30s %s\n" "sudo cscli metrics" "View local metrics" |
| 4077 | + printf " %-30s %s\n" "sudo cscli decisions list" "View active bans/decisions" |
| 4078 | + printf " %-30s %s\n" "sudo cscli bouncers list" "Check bouncer status" |
| 4079 | + printf " %-30s %s\n" "sudo cscli collections list" "View installed collections" |
| 4080 | + printf " %-30s %s\n" "sudo cscli parsers list" "View installed parsers" |
| 4081 | + printf " %-30s %s\n" "sudo cscli scenarios list" "View active scenarios" |
| 4082 | + printf " %-30s %s\n" "sudo cscli alerts list" "View recent alerts" |
| 4083 | + printf " %-30s %s\n" "sudo cscli hub update && sudo cscli hub upgrade" "Update CrowdSec scenarios" |
| 4084 | + printf '\n' |
| 4085 | + log "CrowdSec configuration completed." |
| 4086 | +} |
| 4087 | + |
3990 | 4088 | configure_auto_updates() { |
3991 | 4089 | print_section "Automatic Security Updates" |
3992 | 4090 | if confirm "Enable automatic security updates via unattended-upgrades?"; then |
@@ -5253,14 +5351,39 @@ generate_summary() { |
5253 | 5351 | printf '\n' |
5254 | 5352 |
|
5255 | 5353 | print_separator "Final Service Status Check:" |
5256 | | - for service in "$SSH_SERVICE" fail2ban chrony; do |
| 5354 | + for service in "$SSH_SERVICE" chrony; do |
5257 | 5355 | if systemctl is-active --quiet "$service"; then |
5258 | 5356 | printf " %-20s ${GREEN}✓ Active${NC}\n" "$service" |
5259 | 5357 | else |
5260 | 5358 | printf " %-20s ${RED}✗ INACTIVE${NC}\n" "$service" |
5261 | 5359 | FAILED_SERVICES+=("$service") |
5262 | 5360 | fi |
5263 | 5361 | done |
| 5362 | + if [[ "$IDS_INSTALLED" == "fail2ban" ]] || systemctl is-active --quiet fail2ban; then |
| 5363 | + if systemctl is-active --quiet fail2ban; then |
| 5364 | + printf " %-20s ${GREEN}✓ Active${NC}\n" "Fail2Ban" |
| 5365 | + else |
| 5366 | + printf " %-20s ${RED}✗ INACTIVE${NC}\n" "Fail2Ban" |
| 5367 | + FAILED_SERVICES+=("fail2ban") |
| 5368 | + fi |
| 5369 | + fi |
| 5370 | + |
| 5371 | + if [[ "$IDS_INSTALLED" == "crowdsec" ]] || systemctl is-active --quiet crowdsec; then |
| 5372 | + if systemctl is-active --quiet crowdsec; then |
| 5373 | + printf " %-20s ${GREEN}✓ Active${NC}\n" "CrowdSec" |
| 5374 | + # Check bouncer |
| 5375 | + if command -v cscli >/dev/null; then |
| 5376 | + if cscli bouncers list -o json | grep -q "firewall-bouncer"; then |
| 5377 | + printf " %-20s ${GREEN}✓ Active${NC}\n" "CrowdSec Firewall" |
| 5378 | + else |
| 5379 | + printf " %-20s ${YELLOW}⚠ Bouncer Missing${NC}\n" "CrowdSec Firewall" |
| 5380 | + fi |
| 5381 | + fi |
| 5382 | + else |
| 5383 | + printf " %-20s ${RED}✗ INACTIVE${NC}\n" "CrowdSec" |
| 5384 | + FAILED_SERVICES+=("crowdsec") |
| 5385 | + fi |
| 5386 | + fi |
5264 | 5387 | if ufw status | grep -q "Status: active"; then |
5265 | 5388 | printf " %-20s ${GREEN}✓ Active${NC}\n" "ufw (firewall)" |
5266 | 5389 | else |
@@ -5455,8 +5578,13 @@ generate_summary() { |
5455 | 5578 | # Other verification commands |
5456 | 5579 | printf " %-28s ${CYAN}%s${NC}\n" "- Firewall rules:" "sudo ufw status verbose" |
5457 | 5580 | printf " %-28s ${CYAN}%s${NC}\n" "- Time sync:" "chronyc tracking" |
5458 | | - printf " %-28s ${CYAN}%s${NC}\n" "- Fail2Ban sshd jail:" "sudo fail2ban-client status sshd" |
5459 | | - printf " %-28s ${CYAN}%s${NC}\n" "- Fail2Ban ufw jail:" "sudo fail2ban-client status ufw-probes" |
| 5581 | + # Adjust verification commands based on selection |
| 5582 | + if [[ "$IDS_INSTALLED" == "fail2ban" ]]; then |
| 5583 | + printf " %-28s ${CYAN}%s${NC}\n" "- Fail2Ban sshd jail:" "sudo fail2ban-client status sshd" |
| 5584 | + elif [[ "$IDS_INSTALLED" == "crowdsec" ]]; then |
| 5585 | + printf " %-28s ${CYAN}%s${NC}\n" "- CrowdSec status:" "sudo cscli metrics" |
| 5586 | + printf " %-28s ${CYAN}%s${NC}\n" "- CrowdSec bans:" "sudo cscli decisions list" |
| 5587 | + fi |
5460 | 5588 | printf " %-28s ${CYAN}%s${NC}\n" "- Swap status:" "sudo swapon --show && free -h" |
5461 | 5589 | printf " %-28s ${CYAN}%s${NC}\n" "- Kernel settings:" "sudo sysctl fs.protected_hardlinks kernel.yama.ptrace_scope" |
5462 | 5590 | if command -v docker >/dev/null 2>&1; then |
@@ -5562,7 +5690,31 @@ main() { |
5562 | 5690 | setup_user |
5563 | 5691 | configure_system |
5564 | 5692 | configure_firewall |
5565 | | - configure_fail2ban |
| 5693 | + # --- Choose Firewall fail2ban/CrowdSec --- |
| 5694 | + print_section "Intrusion Detection System (IDS)" |
| 5695 | + printf '%s\n' "${CYAN}Choose an Intrusion Detection/Prevention System:${NC}" |
| 5696 | + printf ' 1) Fail2Ban (Classic, simple log parsing, standalone)\n' |
| 5697 | + printf ' 2) CrowdSec (Modern, collaborative reputation database, highly recommended)\n' |
| 5698 | + printf ' 3) Skip IDS setup\n' |
| 5699 | + |
| 5700 | + local IDS_CHOICE |
| 5701 | + read -rp "$(printf '%s' "${CYAN}Enter choice [1]: ${NC}")" IDS_CHOICE |
| 5702 | + IDS_CHOICE=${IDS_CHOICE:-1} |
| 5703 | + |
| 5704 | + case "$IDS_CHOICE" in |
| 5705 | + 1) |
| 5706 | + configure_fail2ban |
| 5707 | + IDS_INSTALLED="fail2ban" |
| 5708 | + ;; |
| 5709 | + 2) |
| 5710 | + configure_crowdsec |
| 5711 | + IDS_INSTALLED="crowdsec" |
| 5712 | + ;; |
| 5713 | + *) |
| 5714 | + print_info "Skipping Intrusion Detection System setup." |
| 5715 | + IDS_INSTALLED="none" |
| 5716 | + ;; |
| 5717 | + esac |
5566 | 5718 | configure_ssh |
5567 | 5719 | configure_auto_updates |
5568 | 5720 | configure_time_sync |
|
0 commit comments