Skip to content

Instantly share code, notes, and snippets.

@amosbastian
Last active August 15, 2025 16:21
Show Gist options
  • Select an option

  • Save amosbastian/8cc3efd809cc244b4213bfc46b5e74f3 to your computer and use it in GitHub Desktop.

Select an option

Save amosbastian/8cc3efd809cc244b4213bfc46b5e74f3 to your computer and use it in GitHub Desktop.

Revisions

  1. amosbastian revised this gist Aug 15, 2025. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions hardening.sh
    Original file line number Diff line number Diff line change
    @@ -245,7 +245,7 @@ EOF

    # Install Docker
    log "Installing Docker..."
    apt-get install -y ca-certificates curl
    apt-get install ca-certificates curl
    install -m 0755 -d /etc/apt/keyrings
    curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
    chmod a+r /etc/apt/keyrings/docker.asc
    @@ -256,7 +256,7 @@ echo \
    $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
    tee /etc/apt/sources.list.d/docker.list > /dev/null
    apt-get update
    apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
    apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

    # Add admin to docker group
    usermod -aG docker "$NEW_USER"
  2. amosbastian revised this gist Jul 18, 2025. 1 changed file with 42 additions and 23 deletions.
    65 changes: 42 additions & 23 deletions hardening.sh
    Original file line number Diff line number Diff line change
    @@ -35,7 +35,7 @@ log "Starting Hetzner server hardening for Debian 12..."

    # Update system and install essentials
    log "Updating system and installing essentials..."
    apt-get update && apt-get install -y fail2ban sudo ansible curl wget git vim htop unattended-upgrades ufw python3-systemd
    apt-get update && apt-get install -y fail2ban sudo ansible curl wget git vim htop unattended-upgrades ufw python3-systemd libpam-modules libpam-modules

    # Create admin user
    log "Creating user '$NEW_USER'..."
    @@ -61,6 +61,15 @@ else
    warn "No authorized_keys found in /root/.ssh/ - you'll need to add your public key manually"
    fi

    # Configure UFW before running Ansible
    log "Configuring UFW firewall..."
    ufw --force reset
    ufw default deny incoming
    ufw default allow outgoing
    ufw allow ssh
    ufw --force enable
    log "UFW configured and enabled with default deny policy"

    # Install DevSec hardening collection
    log "Installing DevSec hardening collection..."
    ansible-galaxy collection install devsec.hardening
    @@ -77,7 +86,7 @@ cat > hardening_playbook.yml << 'EOF'
    vars:
    ssh_allow_users: "admin"
    ssh_allow_groups: "admin"
    # Debian 12 specific SSH configuration
    # Debian 12 specific SSH configuration with PAM enabled
    ssh_server_ports: ['22']
    ssh_use_pam: true
    ssh_challenge_response_authentication: false
    @@ -90,21 +99,28 @@ cat > hardening_playbook.yml << 'EOF'
    ssh_use_dns: false
    ssh_permit_tunnel: "no"
    ssh_print_motd: false
    ssh_password_authentication: false
    ssh_permit_root_login: "no"
    ssh_permit_empty_passwords: false
    # Aggressive fail2ban configuration for better protection
    fail2ban_jail_local: |
    [DEFAULT]
    # Debian 12 uses systemd journal by default
    backend = systemd
    # Aggressive mode settings
    bantime = 86400
    findtime = 300
    maxretry = 2
    [sshd]
    enabled = true
    port = {{ ssh_server_ports | first | default('22') }}
    filter = sshd
    # Debian 12 journal backend
    backend = systemd
    maxretry = 3
    findtime = 600
    bantime = 3600
    maxretry = 2
    findtime = 300
    bantime = 86400
    ignoreip = 127.0.0.1/8 ::1
    pre_tasks:
    @@ -120,6 +136,7 @@ cat > hardening_playbook.yml << 'EOF'
    - ufw
    - unattended-upgrades
    - apt-listchanges
    - libpam-modules
    state: present
    - name: Ensure fail2ban directories exist
    @@ -138,7 +155,16 @@ cat > hardening_playbook.yml << 'EOF'
    - devsec.hardening.ssh_hardening
    tasks:
    - name: Configure Fail2Ban for SSH (Debian 12 compatible)
    - name: Ensure SSH uses PAM
    lineinfile:
    path: /etc/ssh/sshd_config
    regexp: '^#?UsePAM'
    line: 'UsePAM yes'
    backup: yes
    notify:
    - Restart SSH
    - name: Configure Fail2Ban with aggressive settings
    copy:
    dest: /etc/fail2ban/jail.local
    content: "{{ fail2ban_jail_local }}"
    @@ -162,21 +188,7 @@ cat > hardening_playbook.yml << 'EOF'
    enabled: yes
    daemon_reload: yes
    - name: Configure UFW default policies
    ufw:
    direction: "{{ item.direction }}"
    policy: "{{ item.policy }}"
    loop:
    - { direction: 'incoming', policy: 'deny' }
    - { direction: 'outgoing', policy: 'allow' }
    - name: Allow SSH through UFW
    ufw:
    rule: allow
    port: "{{ ssh_server_ports | first | default('22') }}"
    proto: tcp
    - name: Enable UFW
    - name: Verify UFW is enabled and configured
    ufw:
    state: enabled
    @@ -223,6 +235,12 @@ cat > hardening_playbook.yml << 'EOF'
    name: fail2ban
    state: restarted
    daemon_reload: yes
    - name: Restart SSH
    systemd:
    name: ssh
    state: restarted
    daemon_reload: yes
    EOF

    # Install Docker
    @@ -251,7 +269,8 @@ ansible-playbook hardening_playbook.yml
    log "Checking services status..."
    systemctl status ssh --no-pager
    systemctl status fail2ban --no-pager
    ufw status verbose

    log "Server hardening completed successfully!"
    warn "IMPORTANT: Test SSH connection as '$NEW_USER' user before logging out!"
    log "Your server is now hardened with DevSec playbook, fail2ban, UFW, and Docker installed."
    log "Your server is now hardened with DevSec playbook, fail2ban (aggressive mode), UFW firewall, and Docker installed."
  3. amosbastian revised this gist Jul 18, 2025. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion hardening.sh
    Original file line number Diff line number Diff line change
    @@ -35,7 +35,7 @@ log "Starting Hetzner server hardening for Debian 12..."

    # Update system and install essentials
    log "Updating system and installing essentials..."
    apt-get update && apt-get install -y fail2ban sudo ansible curl wget git vim htop unattended-upgrades ufw
    apt-get update && apt-get install -y fail2ban sudo ansible curl wget git vim htop unattended-upgrades ufw python3-systemd

    # Create admin user
    log "Creating user '$NEW_USER'..."
  4. amosbastian revised this gist Jul 18, 2025. 1 changed file with 5 additions and 5 deletions.
    10 changes: 5 additions & 5 deletions hardening.sh
    Original file line number Diff line number Diff line change
    @@ -29,15 +29,15 @@ if [[ $EUID -ne 0 ]]; then
    error "This script must be run as root"
    fi

    NEW_USER="amos"
    NEW_USER="admin"

    log "Starting Hetzner server hardening for Debian 12..."

    # Update system and install essentials
    log "Updating system and installing essentials..."
    apt-get update && apt-get install -y fail2ban sudo ansible curl wget git vim htop unattended-upgrades ufw

    # Create amos user
    # Create admin user
    log "Creating user '$NEW_USER'..."
    if ! id "$NEW_USER" &>/dev/null; then
    useradd -m -s /bin/bash "$NEW_USER"
    @@ -75,8 +75,8 @@ cat > hardening_playbook.yml << 'EOF'
    collections:
    - devsec.hardening
    vars:
    ssh_allow_users: "amos"
    ssh_allow_groups: "amos"
    ssh_allow_users: "admin"
    ssh_allow_groups: "admin"
    # Debian 12 specific SSH configuration
    ssh_server_ports: ['22']
    ssh_use_pam: true
    @@ -240,7 +240,7 @@ echo \
    apt-get update
    apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

    # Add amos to docker group
    # Add admin to docker group
    usermod -aG docker "$NEW_USER"

    # Run the hardening playbook
  5. amosbastian revised this gist Jul 18, 2025. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion hardening.sh
    Original file line number Diff line number Diff line change
    @@ -88,7 +88,7 @@ cat > hardening_playbook.yml << 'EOF'
    ssh_client_alive_count_max: 3
    ssh_compression: false
    ssh_use_dns: false
    ssh_permit_tunnel: false
    ssh_permit_tunnel: "no"
    ssh_print_motd: false
    fail2ban_jail_local: |
  6. amosbastian revised this gist Jul 18, 2025. 1 changed file with 74 additions and 288 deletions.
    362 changes: 74 additions & 288 deletions hardening.sh
    Original file line number Diff line number Diff line change
    @@ -1,34 +1,26 @@
    #!/bin/bash

    # Hetzner Server Hardening Setup Script
    # Run this script as root after initial SSH connection
    # Hetzner Server Hardening Script for Debian 12
    # Run as root after SSH'ing in: ./harden.sh

    set -e # Exit on any error

    # Configuration
    NEW_USER="${1:-amos}" # Accept username as argument or default to 'amos'
    SSH_PORT="${2:-22}" # Accept SSH port as argument or default to 22
    PLAYBOOK_FILE="hardening_playbook.yml"
    LOG_FILE="/var/log/server-hardening.log"
    set -euo pipefail

    # Colors for output
    RED='\033[0;31m'
    GREEN='\033[0;32m'
    YELLOW='\033[1;33m'
    BLUE='\033[0;34m'
    NC='\033[0m' # No Color

    # Logging function
    log() {
    echo -e "${GREEN}[$(date '+%Y-%m-%d %H:%M:%S')]${NC} $1" | tee -a "$LOG_FILE"
    echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')] $1${NC}"
    }

    warn() {
    echo -e "${YELLOW}[$(date '+%Y-%m-%d %H:%M:%S')] WARNING:${NC} $1" | tee -a "$LOG_FILE"
    echo -e "${YELLOW}[$(date +'%Y-%m-%d %H:%M:%S')] WARNING: $1${NC}"
    }

    error() {
    echo -e "${RED}[$(date '+%Y-%m-%d %H:%M:%S')] ERROR:${NC} $1" | tee -a "$LOG_FILE"
    echo -e "${RED}[$(date +'%Y-%m-%d %H:%M:%S')] ERROR: $1${NC}"
    exit 1
    }

    @@ -37,114 +29,56 @@ if [[ $EUID -ne 0 ]]; then
    error "This script must be run as root"
    fi

    # Usage information
    if [[ "$1" == "-h" || "$1" == "--help" ]]; then
    echo "Usage: $0 [username] [ssh_port]"
    echo "Example: $0 myuser 2222"
    echo "Defaults: username=amos, ssh_port=22"
    exit 0
    fi

    log "πŸš€ Starting Hetzner server hardening setup..."
    log "πŸ‘€ User: $NEW_USER"
    log "πŸ”Œ SSH Port: $SSH_PORT"

    # System checks
    log "πŸ” Performing system checks..."
    if ! command -v systemctl &> /dev/null; then
    error "systemctl not found. This script requires systemd."
    fi

    # Check available disk space
    AVAILABLE_SPACE=$(df / | awk 'NR==2 {print $4}')
    if [[ $AVAILABLE_SPACE -lt 1000000 ]]; then # Less than 1GB
    warn "Low disk space detected. Consider upgrading your server."
    fi
    NEW_USER="amos"

    # Backup existing configs
    log "πŸ’Ύ Creating configuration backup..."
    mkdir -p /root/backup-$(date +%Y%m%d)
    cp -r /etc/ssh /root/backup-$(date +%Y%m%d)/ 2>/dev/null || true
    cp /etc/sudoers /root/backup-$(date +%Y%m%d)/ 2>/dev/null || true
    log "Starting Hetzner server hardening for Debian 12..."

    # Update system and install essential packages
    log "πŸ“¦ Updating system and installing packages..."
    export DEBIAN_FRONTEND=noninteractive
    apt-get update -y
    apt-get upgrade -y
    apt-get install -y fail2ban sudo ansible python3-pip curl wget htop neofetch \
    apt-transport-https ca-certificates software-properties-common \
    vim nano git tree
    # Update system and install essentials
    log "Updating system and installing essentials..."
    apt-get update && apt-get install -y fail2ban sudo ansible curl wget git vim htop unattended-upgrades ufw

    # Create new user
    log "πŸ‘€ Creating user: $NEW_USER"
    if id "$NEW_USER" &>/dev/null; then
    warn "User $NEW_USER already exists, skipping creation"
    else
    # Create amos user
    log "Creating user '$NEW_USER'..."
    if ! id "$NEW_USER" &>/dev/null; then
    useradd -m -s /bin/bash "$NEW_USER"
    usermod -aG sudo "$NEW_USER"
    echo "$NEW_USER ALL=(ALL) NOPASSWD:ALL" > "/etc/sudoers.d/$NEW_USER"

    # Set up SSH for the new user
    log "πŸ”‘ Setting up SSH access for $NEW_USER"
    mkdir -p "/home/$NEW_USER/.ssh"
    if [ -f "/root/.ssh/authorized_keys" ]; then
    cp /root/.ssh/authorized_keys "/home/$NEW_USER/.ssh/"
    chmod 700 "/home/$NEW_USER/.ssh"
    chmod 600 "/home/$NEW_USER/.ssh/authorized_keys"
    chown -R "$NEW_USER:$NEW_USER" "/home/$NEW_USER/.ssh"
    log "βœ… SSH keys copied for $NEW_USER"
    else
    warn "No SSH keys found in /root/.ssh/authorized_keys"
    log "You'll need to set up SSH keys manually for $NEW_USER"
    fi

    # Create a basic bashrc with useful aliases
    cat > "/home/$NEW_USER/.bashrc" << 'BASHRC'
    # Basic bashrc with useful aliases
    export HISTSIZE=10000
    export HISTFILESIZE=10000
    export HISTCONTROL=ignoredups:erasedups
    alias ll='ls -alF'
    alias la='ls -A'
    alias l='ls -CF'
    alias grep='grep --color=auto'
    alias fgrep='fgrep --color=auto'
    alias egrep='egrep --color=auto'
    alias ..='cd ..'
    alias ...='cd ../..'
    alias ports='netstat -tuln'
    alias pscpu='ps auxf | sort -nr -k 3'
    alias psmem='ps auxf | sort -nr -k 4'
    alias logs='journalctl -f'
    alias sshstatus='sudo systemctl status ssh'
    alias f2bstatus='sudo systemctl status fail2ban'
    alias ufwstatus='sudo ufw status'
    log "User '$NEW_USER' created and added to sudo group"
    else
    log "User '$NEW_USER' already exists"
    fi

    # Show system info on login
    neofetch 2>/dev/null || echo "Welcome to $(hostname)!"
    BASHRC
    chown "$NEW_USER:$NEW_USER" "/home/$NEW_USER/.bashrc"
    # Copy SSH keys from root to new user
    log "Setting up SSH for $NEW_USER user..."
    mkdir -p "/home/$NEW_USER/.ssh"
    if [ -f /root/.ssh/authorized_keys ]; then
    cp /root/.ssh/authorized_keys "/home/$NEW_USER/.ssh/"
    chmod 700 "/home/$NEW_USER/.ssh"
    chmod 600 "/home/$NEW_USER/.ssh/authorized_keys"
    chown -R "$NEW_USER:$NEW_USER" "/home/$NEW_USER/.ssh"
    log "SSH keys copied from root to $NEW_USER"
    else
    warn "No authorized_keys found in /root/.ssh/ - you'll need to add your public key manually"
    fi

    # Install DevSec hardening collection
    log "πŸ›‘οΈ Installing DevSec hardening collection..."
    ansible-galaxy collection install devsec.hardening --force
    log "Installing DevSec hardening collection..."
    ansible-galaxy collection install devsec.hardening

    # Create the hardening playbook
    log "πŸ“ Creating Ansible playbook..."
    cat > "$PLAYBOOK_FILE" << EOF
    # Create hardening playbook
    log "Creating hardening playbook..."
    cat > hardening_playbook.yml << 'EOF'
    ---
    - hosts: localhost
    connection: local
    become: yes
    collections:
    - devsec.hardening
    vars:
    ssh_allow_users: "$NEW_USER"
    ssh_allow_groups: "$NEW_USER"
    ssh_server_ports: ['$SSH_PORT']
    ssh_allow_users: "amos"
    ssh_allow_groups: "amos"
    # Debian 12 specific SSH configuration
    ssh_server_ports: ['22']
    ssh_use_pam: true
    ssh_challenge_response_authentication: false
    ssh_gss_api_authentication: false
    @@ -154,38 +88,24 @@ cat > "$PLAYBOOK_FILE" << EOF
    ssh_client_alive_count_max: 3
    ssh_compression: false
    ssh_use_dns: false
    ssh_permit_tunnel: "no"
    ssh_permit_tunnel: false
    ssh_print_motd: false
    ssh_permit_root_login: "no"
    ssh_password_authentication: false
    ssh_permit_empty_passwords: false
    # OS hardening variables
    os_auditd_enabled: true
    os_auditd_max_log_file_action: rotate
    fail2ban_jail_local: |
    [DEFAULT]
    # Debian 12 uses systemd journal by default
    backend = systemd
    bantime = 3600
    findtime = 600
    maxretry = 3
    ignoreip = 127.0.0.1/8 ::1
    [sshd]
    enabled = true
    port = $SSH_PORT
    port = {{ ssh_server_ports | first | default('22') }}
    filter = sshd
    # Debian 12 journal backend
    backend = systemd
    maxretry = 3
    findtime = 600
    bantime = 3600
    [nginx-http-auth]
    enabled = false
    [nginx-limit-req]
    enabled = false
    ignoreip = 127.0.0.1/8 ::1
    pre_tasks:
    - name: Update package cache
    @@ -200,11 +120,6 @@ cat > "$PLAYBOOK_FILE" << EOF
    - ufw
    - unattended-upgrades
    - apt-listchanges
    - logwatch
    - rkhunter
    - chkrootkit
    - aide
    - auditd
    state: present
    - name: Ensure fail2ban directories exist
    @@ -223,7 +138,7 @@ cat > "$PLAYBOOK_FILE" << EOF
    - devsec.hardening.ssh_hardening
    tasks:
    - name: Configure Fail2Ban for SSH
    - name: Configure Fail2Ban for SSH (Debian 12 compatible)
    copy:
    dest: /etc/fail2ban/jail.local
    content: "{{ fail2ban_jail_local }}"
    @@ -258,7 +173,7 @@ cat > "$PLAYBOOK_FILE" << EOF
    - name: Allow SSH through UFW
    ufw:
    rule: allow
    port: "$SSH_PORT"
    port: "{{ ssh_server_ports | first | default('22') }}"
    proto: tcp
    - name: Enable UFW
    @@ -270,10 +185,10 @@ cat > "$PLAYBOOK_FILE" << EOF
    dest: /etc/apt/apt.conf.d/50unattended-upgrades
    content: |
    Unattended-Upgrade::Allowed-Origins {
    "\${distro_id}:\${distro_codename}";
    "\${distro_id}:\${distro_codename}-security";
    "\${distro_id}ESMApps:\${distro_codename}-apps-security";
    "\${distro_id}ESM:\${distro_codename}-infra-security";
    "${distro_id}:${distro_codename}";
    "${distro_id}:${distro_codename}-security";
    "${distro_id}ESMApps:${distro_codename}-apps-security";
    "${distro_id}ESM:${distro_codename}-infra-security";
    };
    Unattended-Upgrade::Package-Blacklist {
    };
    @@ -282,8 +197,6 @@ cat > "$PLAYBOOK_FILE" << EOF
    Unattended-Upgrade::Remove-New-Unused-Dependencies "true";
    Unattended-Upgrade::Remove-Unused-Dependencies "true";
    Unattended-Upgrade::Automatic-Reboot "false";
    Unattended-Upgrade::Mail "root";
    Unattended-Upgrade::MailOnlyOnError "true";
    owner: root
    group: root
    mode: '0644'
    @@ -294,70 +207,10 @@ cat > "$PLAYBOOK_FILE" << EOF
    content: |
    APT::Periodic::Update-Package-Lists "1";
    APT::Periodic::Unattended-Upgrade "1";
    APT::Periodic::Download-Upgradeable-Packages "1";
    APT::Periodic::AutocleanInterval "7";
    owner: root
    group: root
    mode: '0644'
    - name: Configure logwatch
    copy:
    dest: /etc/logwatch/conf/logwatch.conf
    content: |
    LogDir = /var/log
    TmpDir = /var/cache/logwatch
    MailTo = root
    MailFrom = logwatch@$(hostname -f)
    Print = No
    Save = /tmp/logwatch
    Range = yesterday
    Detail = Med
    Service = All
    Service = "-zz-network"
    Service = "-zz-sys"
    mailer = "/usr/sbin/sendmail -t"
    owner: root
    group: root
    mode: '0644'
    - name: Initialize AIDE database
    command: aideinit
    args:
    creates: /var/lib/aide/aide.db
    - name: Move AIDE database
    command: mv /var/lib/aide/aide.db.new /var/lib/aide/aide.db
    args:
    creates: /var/lib/aide/aide.db
    removes: /var/lib/aide/aide.db.new
    - name: Create daily security check script
    copy:
    dest: /usr/local/bin/daily-security-check.sh
    content: |
    #!/bin/bash
    echo "=== Daily Security Check - $(date) ===" >> /var/log/security-check.log
    echo "--- Fail2Ban Status ---" >> /var/log/security-check.log
    fail2ban-client status >> /var/log/security-check.log 2>&1
    echo "--- UFW Status ---" >> /var/log/security-check.log
    ufw status >> /var/log/security-check.log 2>&1
    echo "--- Last 10 SSH logins ---" >> /var/log/security-check.log
    last -n 10 >> /var/log/security-check.log 2>&1
    echo "--- Failed login attempts ---" >> /var/log/security-check.log
    grep "Failed password" /var/log/auth.log | tail -10 >> /var/log/security-check.log 2>&1
    echo "=========================" >> /var/log/security-check.log
    owner: root
    group: root
    mode: '0755'
    - name: Add daily security check to cron
    cron:
    name: "Daily security check"
    minute: "0"
    hour: "2"
    job: "/usr/local/bin/daily-security-check.sh"
    user: root
    - name: Reload SSH service
    systemd:
    name: ssh
    @@ -372,100 +225,33 @@ cat > "$PLAYBOOK_FILE" << EOF
    daemon_reload: yes
    EOF

    log "βœ… Playbook created: $PLAYBOOK_FILE"
    # Install Docker
    log "Installing Docker..."
    apt-get install -y ca-certificates curl
    install -m 0755 -d /etc/apt/keyrings
    curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
    chmod a+r /etc/apt/keyrings/docker.asc

    # Run the hardening playbook
    log "πŸ”’ Running hardening playbook..."
    if ansible-playbook "$PLAYBOOK_FILE" | tee -a "$LOG_FILE"; then
    log "βœ… Hardening playbook completed successfully"
    else
    error "❌ Hardening playbook failed"
    fi
    # Add the repository to Apt sources:
    echo \
    "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian \
    $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
    tee /etc/apt/sources.list.d/docker.list > /dev/null
    apt-get update
    apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

    # Check service status
    log "πŸ” Checking service status..."
    systemctl is-active --quiet ssh && log "βœ… SSH service is running" || warn "❌ SSH service is not running"
    systemctl is-active --quiet fail2ban && log "βœ… Fail2Ban service is running" || warn "❌ Fail2Ban service is not running"
    systemctl is-active --quiet ufw && log "βœ… UFW service is running" || warn "❌ UFW service is not running"
    # Add amos to docker group
    usermod -aG docker "$NEW_USER"

    # Generate connection test script
    log "πŸ“ Creating connection test script..."
    cat > "/home/$NEW_USER/test-connection.sh" << TESTSCRIPT
    #!/bin/bash
    echo "Testing SSH connection to this server..."
    echo "Run this from your local machine:"
    echo "ssh -p $SSH_PORT $NEW_USER@\$(curl -s ifconfig.me)"
    echo ""
    echo "If connection fails, check:"
    echo "1. UFW status: sudo ufw status"
    echo "2. SSH service: sudo systemctl status ssh"
    echo "3. Fail2Ban logs: sudo journalctl -u fail2ban -f"
    TESTSCRIPT
    chmod +x "/home/$NEW_USER/test-connection.sh"
    chown "$NEW_USER:$NEW_USER" "/home/$NEW_USER/test-connection.sh"

    # Create security status script
    cat > "/home/$NEW_USER/security-status.sh" << SECSCRIPT
    #!/bin/bash
    echo "=== Security Status ==="
    echo "SSH Service:" && sudo systemctl status ssh --no-pager -l
    echo ""
    echo "Fail2Ban Status:" && sudo fail2ban-client status
    echo ""
    echo "UFW Status:" && sudo ufw status
    echo ""
    echo "Last 10 SSH connections:" && last -n 10
    echo ""
    echo "Failed login attempts today:" && sudo grep "Failed password" /var/log/auth.log | grep "\$(date +%b\ %d)" | wc -l
    SECSCRIPT
    chmod +x "/home/$NEW_USER/security-status.sh"
    chown "$NEW_USER:$NEW_USER" "/home/$NEW_USER/security-status.sh"

    # Final security recommendations
    SERVER_IP=$(curl -s ifconfig.me 2>/dev/null || hostname -I | awk '{print $1}')
    # Run the hardening playbook
    log "Running DevSec hardening playbook..."
    ansible-playbook hardening_playbook.yml

    log ""
    log "πŸŽ‰ Server hardening complete!"
    log ""
    log "πŸ“‹ Summary:"
    log " β€’ Created user: $NEW_USER"
    log " β€’ SSH port: $SSH_PORT"
    log " β€’ Configured SSH hardening (root login disabled)"
    log " β€’ Enabled Fail2Ban with intrusion detection"
    log " β€’ Configured UFW firewall"
    log " β€’ Enabled automatic security updates"
    log " β€’ Added system monitoring (AIDE, logwatch, auditd)"
    log " β€’ Created daily security check script"
    log ""
    log "πŸ” Security features enabled:"
    log " β€’ SSH key-only authentication"
    log " β€’ Failed login monitoring"
    log " β€’ File integrity monitoring (AIDE)"
    log " β€’ System auditing (auditd)"
    log " β€’ Rootkit detection (rkhunter, chkrootkit)"
    log " β€’ Log monitoring (logwatch)"
    log ""
    log "πŸ› οΈ Useful commands for $NEW_USER:"
    log " β€’ Check security status: ./security-status.sh"
    log " β€’ Test SSH connection: ./test-connection.sh"
    log " β€’ View logs: sudo journalctl -f"
    log " β€’ Check fail2ban: sudo fail2ban-client status sshd"
    log " β€’ Run file integrity check: sudo aide --check"
    log ""
    log "πŸ”— Connection details:"
    log " β€’ SSH command: ssh -p $SSH_PORT $NEW_USER@$SERVER_IP"
    log " β€’ Make sure to test this connection before logging out!"
    log ""
    log "πŸ“Š Next steps:"
    log " β€’ Test SSH access with new user"
    log " β€’ Consider setting up monitoring alerts"
    log " β€’ Review security logs regularly"
    log " β€’ Keep system updated"
    log ""
    warn "⚠️ CRITICAL: Test SSH access as $NEW_USER before closing this session!"
    warn "⚠️ Root SSH login has been disabled for security"
    # Check services status
    log "Checking services status..."
    systemctl status ssh --no-pager
    systemctl status fail2ban --no-pager

    # Final connection test prompt
    read -p "Press Enter to continue after testing SSH connection as $NEW_USER..."
    log "πŸ”’ Server hardening setup completed successfully!"
    log "πŸ“ Full log available at: $LOG_FILE"
    log "Server hardening completed successfully!"
    warn "IMPORTANT: Test SSH connection as '$NEW_USER' user before logging out!"
    log "Your server is now hardened with DevSec playbook, fail2ban, UFW, and Docker installed."
  7. amosbastian revised this gist Jul 18, 2025. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions hardening.sh
    Original file line number Diff line number Diff line change
    @@ -154,9 +154,9 @@ cat > "$PLAYBOOK_FILE" << EOF
    ssh_client_alive_count_max: 3
    ssh_compression: false
    ssh_use_dns: false
    ssh_permit_tunnel: false
    ssh_permit_tunnel: "no"
    ssh_print_motd: false
    ssh_permit_root_login: false
    ssh_permit_root_login: "no"
    ssh_password_authentication: false
    ssh_permit_empty_passwords: false
  8. amosbastian revised this gist Jul 18, 2025. 1 changed file with 283 additions and 48 deletions.
    331 changes: 283 additions & 48 deletions hardening.sh
    Original file line number Diff line number Diff line change
    @@ -6,56 +6,145 @@
    set -e # Exit on any error

    # Configuration
    NEW_USER="amos" # Change this to your desired username
    NEW_USER="${1:-amos}" # Accept username as argument or default to 'amos'
    SSH_PORT="${2:-22}" # Accept SSH port as argument or default to 22
    PLAYBOOK_FILE="hardening_playbook.yml"
    LOG_FILE="/var/log/server-hardening.log"

    echo "πŸš€ Starting Hetzner server hardening setup..."
    # Colors for output
    RED='\033[0;31m'
    GREEN='\033[0;32m'
    YELLOW='\033[1;33m'
    BLUE='\033[0;34m'
    NC='\033[0m' # No Color

    # Logging function
    log() {
    echo -e "${GREEN}[$(date '+%Y-%m-%d %H:%M:%S')]${NC} $1" | tee -a "$LOG_FILE"
    }

    warn() {
    echo -e "${YELLOW}[$(date '+%Y-%m-%d %H:%M:%S')] WARNING:${NC} $1" | tee -a "$LOG_FILE"
    }

    error() {
    echo -e "${RED}[$(date '+%Y-%m-%d %H:%M:%S')] ERROR:${NC} $1" | tee -a "$LOG_FILE"
    exit 1
    }

    # Check if running as root
    if [[ $EUID -ne 0 ]]; then
    error "This script must be run as root"
    fi

    # Usage information
    if [[ "$1" == "-h" || "$1" == "--help" ]]; then
    echo "Usage: $0 [username] [ssh_port]"
    echo "Example: $0 myuser 2222"
    echo "Defaults: username=amos, ssh_port=22"
    exit 0
    fi

    log "πŸš€ Starting Hetzner server hardening setup..."
    log "πŸ‘€ User: $NEW_USER"
    log "πŸ”Œ SSH Port: $SSH_PORT"

    # System checks
    log "πŸ” Performing system checks..."
    if ! command -v systemctl &> /dev/null; then
    error "systemctl not found. This script requires systemd."
    fi

    # Check available disk space
    AVAILABLE_SPACE=$(df / | awk 'NR==2 {print $4}')
    if [[ $AVAILABLE_SPACE -lt 1000000 ]]; then # Less than 1GB
    warn "Low disk space detected. Consider upgrading your server."
    fi

    # Backup existing configs
    log "πŸ’Ύ Creating configuration backup..."
    mkdir -p /root/backup-$(date +%Y%m%d)
    cp -r /etc/ssh /root/backup-$(date +%Y%m%d)/ 2>/dev/null || true
    cp /etc/sudoers /root/backup-$(date +%Y%m%d)/ 2>/dev/null || true

    # Update system and install essential packages
    echo "πŸ“¦ Updating system and installing packages..."
    log "πŸ“¦ Updating system and installing packages..."
    export DEBIAN_FRONTEND=noninteractive
    apt-get update -y
    apt-get install -y fail2ban sudo ansible python3-pip
    apt-get upgrade -y
    apt-get install -y fail2ban sudo ansible python3-pip curl wget htop neofetch \
    apt-transport-https ca-certificates software-properties-common \
    vim nano git tree

    # Create new user
    echo "πŸ‘€ Creating user: $NEW_USER"
    log "πŸ‘€ Creating user: $NEW_USER"
    if id "$NEW_USER" &>/dev/null; then
    echo "User $NEW_USER already exists, skipping creation"
    warn "User $NEW_USER already exists, skipping creation"
    else
    useradd -m -s /bin/bash "$NEW_USER"
    usermod -aG sudo "$NEW_USER"
    echo "$NEW_USER ALL=(ALL) NOPASSWD:ALL" > "/etc/sudoers.d/$NEW_USER"

    # Set up SSH for the new user
    echo "πŸ”‘ Setting up SSH access for $NEW_USER"
    log "πŸ”‘ Setting up SSH access for $NEW_USER"
    mkdir -p "/home/$NEW_USER/.ssh"
    if [ -f "/root/.ssh/authorized_keys" ]; then
    cp /root/.ssh/authorized_keys "/home/$NEW_USER/.ssh/"
    chmod 700 "/home/$NEW_USER/.ssh"
    chmod 600 "/home/$NEW_USER/.ssh/authorized_keys"
    chown -R "$NEW_USER:$NEW_USER" "/home/$NEW_USER/.ssh"
    echo "βœ… SSH keys copied for $NEW_USER"
    log "βœ… SSH keys copied for $NEW_USER"
    else
    echo "⚠️ No SSH keys found in /root/.ssh/authorized_keys"
    warn "No SSH keys found in /root/.ssh/authorized_keys"
    log "You'll need to set up SSH keys manually for $NEW_USER"
    fi

    # Create a basic bashrc with useful aliases
    cat > "/home/$NEW_USER/.bashrc" << 'BASHRC'
    # Basic bashrc with useful aliases
    export HISTSIZE=10000
    export HISTFILESIZE=10000
    export HISTCONTROL=ignoredups:erasedups
    alias ll='ls -alF'
    alias la='ls -A'
    alias l='ls -CF'
    alias grep='grep --color=auto'
    alias fgrep='fgrep --color=auto'
    alias egrep='egrep --color=auto'
    alias ..='cd ..'
    alias ...='cd ../..'
    alias ports='netstat -tuln'
    alias pscpu='ps auxf | sort -nr -k 3'
    alias psmem='ps auxf | sort -nr -k 4'
    alias logs='journalctl -f'
    alias sshstatus='sudo systemctl status ssh'
    alias f2bstatus='sudo systemctl status fail2ban'
    alias ufwstatus='sudo ufw status'
    # Show system info on login
    neofetch 2>/dev/null || echo "Welcome to $(hostname)!"
    BASHRC
    chown "$NEW_USER:$NEW_USER" "/home/$NEW_USER/.bashrc"
    fi

    # Install DevSec hardening collection
    echo "πŸ›‘οΈ Installing DevSec hardening collection..."
    ansible-galaxy collection install devsec.hardening
    log "πŸ›‘οΈ Installing DevSec hardening collection..."
    ansible-galaxy collection install devsec.hardening --force

    # Create the hardening playbook
    echo "πŸ“ Creating Ansible playbook..."
    cat > "$PLAYBOOK_FILE" << 'EOF'
    log "πŸ“ Creating Ansible playbook..."
    cat > "$PLAYBOOK_FILE" << EOF
    ---
    - hosts: localhost
    connection: local
    become: yes
    collections:
    - devsec.hardening
    vars:
    ssh_allow_users: "amos" # Change this to your username
    ssh_allow_groups: "amos" # Change this to your username
    ssh_server_ports: ['22']
    ssh_allow_users: "$NEW_USER"
    ssh_allow_groups: "$NEW_USER"
    ssh_server_ports: ['$SSH_PORT']
    ssh_use_pam: true
    ssh_challenge_response_authentication: false
    ssh_gss_api_authentication: false
    @@ -67,20 +156,36 @@ cat > "$PLAYBOOK_FILE" << 'EOF'
    ssh_use_dns: false
    ssh_permit_tunnel: false
    ssh_print_motd: false
    ssh_permit_root_login: false
    ssh_password_authentication: false
    ssh_permit_empty_passwords: false
    # OS hardening variables
    os_auditd_enabled: true
    os_auditd_max_log_file_action: rotate
    fail2ban_jail_local: |
    [DEFAULT]
    backend = systemd
    bantime = 3600
    findtime = 600
    maxretry = 3
    ignoreip = 127.0.0.1/8 ::1
    [sshd]
    enabled = true
    port = {{ ssh_server_ports | first | default('22') }}
    port = $SSH_PORT
    filter = sshd
    backend = systemd
    maxretry = 3
    findtime = 600
    bantime = 3600
    ignoreip = 127.0.0.1/8 ::1
    [nginx-http-auth]
    enabled = false
    [nginx-limit-req]
    enabled = false
    pre_tasks:
    - name: Update package cache
    @@ -95,6 +200,11 @@ cat > "$PLAYBOOK_FILE" << 'EOF'
    - ufw
    - unattended-upgrades
    - apt-listchanges
    - logwatch
    - rkhunter
    - chkrootkit
    - aide
    - auditd
    state: present
    - name: Ensure fail2ban directories exist
    @@ -148,7 +258,7 @@ cat > "$PLAYBOOK_FILE" << 'EOF'
    - name: Allow SSH through UFW
    ufw:
    rule: allow
    port: "{{ ssh_server_ports | first | default('22') }}"
    port: "$SSH_PORT"
    proto: tcp
    - name: Enable UFW
    @@ -160,10 +270,10 @@ cat > "$PLAYBOOK_FILE" << 'EOF'
    dest: /etc/apt/apt.conf.d/50unattended-upgrades
    content: |
    Unattended-Upgrade::Allowed-Origins {
    "${distro_id}:${distro_codename}";
    "${distro_id}:${distro_codename}-security";
    "${distro_id}ESMApps:${distro_codename}-apps-security";
    "${distro_id}ESM:${distro_codename}-infra-security";
    "\${distro_id}:\${distro_codename}";
    "\${distro_id}:\${distro_codename}-security";
    "\${distro_id}ESMApps:\${distro_codename}-apps-security";
    "\${distro_id}ESM:\${distro_codename}-infra-security";
    };
    Unattended-Upgrade::Package-Blacklist {
    };
    @@ -172,6 +282,8 @@ cat > "$PLAYBOOK_FILE" << 'EOF'
    Unattended-Upgrade::Remove-New-Unused-Dependencies "true";
    Unattended-Upgrade::Remove-Unused-Dependencies "true";
    Unattended-Upgrade::Automatic-Reboot "false";
    Unattended-Upgrade::Mail "root";
    Unattended-Upgrade::MailOnlyOnError "true";
    owner: root
    group: root
    mode: '0644'
    @@ -182,10 +294,70 @@ cat > "$PLAYBOOK_FILE" << 'EOF'
    content: |
    APT::Periodic::Update-Package-Lists "1";
    APT::Periodic::Unattended-Upgrade "1";
    APT::Periodic::Download-Upgradeable-Packages "1";
    APT::Periodic::AutocleanInterval "7";
    owner: root
    group: root
    mode: '0644'
    - name: Configure logwatch
    copy:
    dest: /etc/logwatch/conf/logwatch.conf
    content: |
    LogDir = /var/log
    TmpDir = /var/cache/logwatch
    MailTo = root
    MailFrom = logwatch@$(hostname -f)
    Print = No
    Save = /tmp/logwatch
    Range = yesterday
    Detail = Med
    Service = All
    Service = "-zz-network"
    Service = "-zz-sys"
    mailer = "/usr/sbin/sendmail -t"
    owner: root
    group: root
    mode: '0644'
    - name: Initialize AIDE database
    command: aideinit
    args:
    creates: /var/lib/aide/aide.db
    - name: Move AIDE database
    command: mv /var/lib/aide/aide.db.new /var/lib/aide/aide.db
    args:
    creates: /var/lib/aide/aide.db
    removes: /var/lib/aide/aide.db.new
    - name: Create daily security check script
    copy:
    dest: /usr/local/bin/daily-security-check.sh
    content: |
    #!/bin/bash
    echo "=== Daily Security Check - $(date) ===" >> /var/log/security-check.log
    echo "--- Fail2Ban Status ---" >> /var/log/security-check.log
    fail2ban-client status >> /var/log/security-check.log 2>&1
    echo "--- UFW Status ---" >> /var/log/security-check.log
    ufw status >> /var/log/security-check.log 2>&1
    echo "--- Last 10 SSH logins ---" >> /var/log/security-check.log
    last -n 10 >> /var/log/security-check.log 2>&1
    echo "--- Failed login attempts ---" >> /var/log/security-check.log
    grep "Failed password" /var/log/auth.log | tail -10 >> /var/log/security-check.log 2>&1
    echo "=========================" >> /var/log/security-check.log
    owner: root
    group: root
    mode: '0755'
    - name: Add daily security check to cron
    cron:
    name: "Daily security check"
    minute: "0"
    hour: "2"
    job: "/usr/local/bin/daily-security-check.sh"
    user: root
    - name: Reload SSH service
    systemd:
    name: ssh
    @@ -200,37 +372,100 @@ cat > "$PLAYBOOK_FILE" << 'EOF'
    daemon_reload: yes
    EOF

    # Update the playbook with the correct username
    sed -i "s/ssh_allow_users: \"amos\"/ssh_allow_users: \"$NEW_USER\"/" "$PLAYBOOK_FILE"
    sed -i "s/ssh_allow_groups: \"amos\"/ssh_allow_groups: \"$NEW_USER\"/" "$PLAYBOOK_FILE"

    echo "βœ… Playbook created: $PLAYBOOK_FILE"
    log "βœ… Playbook created: $PLAYBOOK_FILE"

    # Run the hardening playbook
    echo "πŸ”’ Running hardening playbook..."
    ansible-playbook "$PLAYBOOK_FILE"
    log "πŸ”’ Running hardening playbook..."
    if ansible-playbook "$PLAYBOOK_FILE" | tee -a "$LOG_FILE"; then
    log "βœ… Hardening playbook completed successfully"
    else
    error "❌ Hardening playbook failed"
    fi

    # Check service status
    echo "πŸ” Checking service status..."
    systemctl status ssh --no-pager -l
    systemctl status fail2ban --no-pager -l
    systemctl status ufw --no-pager -l
    log "πŸ” Checking service status..."
    systemctl is-active --quiet ssh && log "βœ… SSH service is running" || warn "❌ SSH service is not running"
    systemctl is-active --quiet fail2ban && log "βœ… Fail2Ban service is running" || warn "❌ Fail2Ban service is not running"
    systemctl is-active --quiet ufw && log "βœ… UFW service is running" || warn "❌ UFW service is not running"

    # Generate connection test script
    log "πŸ“ Creating connection test script..."
    cat > "/home/$NEW_USER/test-connection.sh" << TESTSCRIPT
    #!/bin/bash
    echo "Testing SSH connection to this server..."
    echo "Run this from your local machine:"
    echo "ssh -p $SSH_PORT $NEW_USER@\$(curl -s ifconfig.me)"
    echo ""
    echo "If connection fails, check:"
    echo "1. UFW status: sudo ufw status"
    echo "2. SSH service: sudo systemctl status ssh"
    echo "3. Fail2Ban logs: sudo journalctl -u fail2ban -f"
    TESTSCRIPT
    chmod +x "/home/$NEW_USER/test-connection.sh"
    chown "$NEW_USER:$NEW_USER" "/home/$NEW_USER/test-connection.sh"

    # Create security status script
    cat > "/home/$NEW_USER/security-status.sh" << SECSCRIPT
    #!/bin/bash
    echo "=== Security Status ==="
    echo "SSH Service:" && sudo systemctl status ssh --no-pager -l
    echo ""
    echo "πŸŽ‰ Server hardening complete!"
    echo "Fail2Ban Status:" && sudo fail2ban-client status
    echo ""
    echo "πŸ“‹ Summary:"
    echo " β€’ Created user: $NEW_USER"
    echo " β€’ Configured SSH hardening"
    echo " β€’ Enabled Fail2Ban"
    echo " β€’ Configured UFW firewall"
    echo " β€’ Enabled automatic security updates"
    echo "UFW Status:" && sudo ufw status
    echo ""
    echo "πŸ” Security recommendations:"
    echo " β€’ Test SSH access with: ssh $NEW_USER@$(hostname -I | awk '{print $1}')"
    echo " β€’ Disable root SSH login after confirming user access works"
    echo " β€’ Consider changing SSH port from default 22"
    echo " β€’ Review UFW rules: sudo ufw status"
    echo " β€’ Monitor logs: sudo journalctl -u fail2ban -f"
    echo "Last 10 SSH connections:" && last -n 10
    echo ""
    echo "⚠️ IMPORTANT: Test SSH access before logging out!"
    echo "Failed login attempts today:" && sudo grep "Failed password" /var/log/auth.log | grep "\$(date +%b\ %d)" | wc -l
    SECSCRIPT
    chmod +x "/home/$NEW_USER/security-status.sh"
    chown "$NEW_USER:$NEW_USER" "/home/$NEW_USER/security-status.sh"

    # Final security recommendations
    SERVER_IP=$(curl -s ifconfig.me 2>/dev/null || hostname -I | awk '{print $1}')

    log ""
    log "πŸŽ‰ Server hardening complete!"
    log ""
    log "πŸ“‹ Summary:"
    log " β€’ Created user: $NEW_USER"
    log " β€’ SSH port: $SSH_PORT"
    log " β€’ Configured SSH hardening (root login disabled)"
    log " β€’ Enabled Fail2Ban with intrusion detection"
    log " β€’ Configured UFW firewall"
    log " β€’ Enabled automatic security updates"
    log " β€’ Added system monitoring (AIDE, logwatch, auditd)"
    log " β€’ Created daily security check script"
    log ""
    log "πŸ” Security features enabled:"
    log " β€’ SSH key-only authentication"
    log " β€’ Failed login monitoring"
    log " β€’ File integrity monitoring (AIDE)"
    log " β€’ System auditing (auditd)"
    log " β€’ Rootkit detection (rkhunter, chkrootkit)"
    log " β€’ Log monitoring (logwatch)"
    log ""
    log "πŸ› οΈ Useful commands for $NEW_USER:"
    log " β€’ Check security status: ./security-status.sh"
    log " β€’ Test SSH connection: ./test-connection.sh"
    log " β€’ View logs: sudo journalctl -f"
    log " β€’ Check fail2ban: sudo fail2ban-client status sshd"
    log " β€’ Run file integrity check: sudo aide --check"
    log ""
    log "πŸ”— Connection details:"
    log " β€’ SSH command: ssh -p $SSH_PORT $NEW_USER@$SERVER_IP"
    log " β€’ Make sure to test this connection before logging out!"
    log ""
    log "πŸ“Š Next steps:"
    log " β€’ Test SSH access with new user"
    log " β€’ Consider setting up monitoring alerts"
    log " β€’ Review security logs regularly"
    log " β€’ Keep system updated"
    log ""
    warn "⚠️ CRITICAL: Test SSH access as $NEW_USER before closing this session!"
    warn "⚠️ Root SSH login has been disabled for security"

    # Final connection test prompt
    read -p "Press Enter to continue after testing SSH connection as $NEW_USER..."
    log "πŸ”’ Server hardening setup completed successfully!"
    log "πŸ“ Full log available at: $LOG_FILE"
  9. amosbastian created this gist Jul 18, 2025.
    236 changes: 236 additions & 0 deletions hardening.sh
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,236 @@
    #!/bin/bash

    # Hetzner Server Hardening Setup Script
    # Run this script as root after initial SSH connection

    set -e # Exit on any error

    # Configuration
    NEW_USER="amos" # Change this to your desired username
    PLAYBOOK_FILE="hardening_playbook.yml"

    echo "πŸš€ Starting Hetzner server hardening setup..."

    # Update system and install essential packages
    echo "πŸ“¦ Updating system and installing packages..."
    apt-get update -y
    apt-get install -y fail2ban sudo ansible python3-pip

    # Create new user
    echo "πŸ‘€ Creating user: $NEW_USER"
    if id "$NEW_USER" &>/dev/null; then
    echo "User $NEW_USER already exists, skipping creation"
    else
    useradd -m -s /bin/bash "$NEW_USER"
    usermod -aG sudo "$NEW_USER"
    echo "$NEW_USER ALL=(ALL) NOPASSWD:ALL" > "/etc/sudoers.d/$NEW_USER"

    # Set up SSH for the new user
    echo "πŸ”‘ Setting up SSH access for $NEW_USER"
    mkdir -p "/home/$NEW_USER/.ssh"
    if [ -f "/root/.ssh/authorized_keys" ]; then
    cp /root/.ssh/authorized_keys "/home/$NEW_USER/.ssh/"
    chmod 700 "/home/$NEW_USER/.ssh"
    chmod 600 "/home/$NEW_USER/.ssh/authorized_keys"
    chown -R "$NEW_USER:$NEW_USER" "/home/$NEW_USER/.ssh"
    echo "βœ… SSH keys copied for $NEW_USER"
    else
    echo "⚠️ No SSH keys found in /root/.ssh/authorized_keys"
    fi
    fi

    # Install DevSec hardening collection
    echo "πŸ›‘οΈ Installing DevSec hardening collection..."
    ansible-galaxy collection install devsec.hardening

    # Create the hardening playbook
    echo "πŸ“ Creating Ansible playbook..."
    cat > "$PLAYBOOK_FILE" << 'EOF'
    ---
    - hosts: localhost
    connection: local
    become: yes
    collections:
    - devsec.hardening
    vars:
    ssh_allow_users: "amos" # Change this to your username
    ssh_allow_groups: "amos" # Change this to your username
    ssh_server_ports: ['22']
    ssh_use_pam: true
    ssh_challenge_response_authentication: false
    ssh_gss_api_authentication: false
    ssh_x11_forwarding: false
    ssh_max_auth_tries: 3
    ssh_client_alive_interval: 600
    ssh_client_alive_count_max: 3
    ssh_compression: false
    ssh_use_dns: false
    ssh_permit_tunnel: false
    ssh_print_motd: false
    fail2ban_jail_local: |
    [DEFAULT]
    backend = systemd
    [sshd]
    enabled = true
    port = {{ ssh_server_ports | first | default('22') }}
    filter = sshd
    backend = systemd
    maxretry = 3
    findtime = 600
    bantime = 3600
    ignoreip = 127.0.0.1/8 ::1
    pre_tasks:
    - name: Update package cache
    apt:
    update_cache: yes
    cache_valid_time: 3600
    - name: Install required packages
    apt:
    name:
    - fail2ban
    - ufw
    - unattended-upgrades
    - apt-listchanges
    state: present
    - name: Ensure fail2ban directories exist
    file:
    path: "{{ item }}"
    state: directory
    owner: root
    group: root
    mode: '0755'
    loop:
    - /etc/fail2ban
    - /etc/fail2ban/jail.d
    roles:
    - devsec.hardening.os_hardening
    - devsec.hardening.ssh_hardening
    tasks:
    - name: Configure Fail2Ban for SSH
    copy:
    dest: /etc/fail2ban/jail.local
    content: "{{ fail2ban_jail_local }}"
    owner: root
    group: root
    mode: '0644'
    notify:
    - Restart Fail2Ban
    - name: Enable and start Fail2Ban service
    systemd:
    name: fail2ban
    state: started
    enabled: yes
    daemon_reload: yes
    - name: Ensure SSH service is enabled and running
    systemd:
    name: ssh
    state: started
    enabled: yes
    daemon_reload: yes
    - name: Configure UFW default policies
    ufw:
    direction: "{{ item.direction }}"
    policy: "{{ item.policy }}"
    loop:
    - { direction: 'incoming', policy: 'deny' }
    - { direction: 'outgoing', policy: 'allow' }
    - name: Allow SSH through UFW
    ufw:
    rule: allow
    port: "{{ ssh_server_ports | first | default('22') }}"
    proto: tcp
    - name: Enable UFW
    ufw:
    state: enabled
    - name: Configure automatic security updates
    copy:
    dest: /etc/apt/apt.conf.d/50unattended-upgrades
    content: |
    Unattended-Upgrade::Allowed-Origins {
    "${distro_id}:${distro_codename}";
    "${distro_id}:${distro_codename}-security";
    "${distro_id}ESMApps:${distro_codename}-apps-security";
    "${distro_id}ESM:${distro_codename}-infra-security";
    };
    Unattended-Upgrade::Package-Blacklist {
    };
    Unattended-Upgrade::DevRelease "false";
    Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";
    Unattended-Upgrade::Remove-New-Unused-Dependencies "true";
    Unattended-Upgrade::Remove-Unused-Dependencies "true";
    Unattended-Upgrade::Automatic-Reboot "false";
    owner: root
    group: root
    mode: '0644'
    - name: Enable automatic updates
    copy:
    dest: /etc/apt/apt.conf.d/20auto-upgrades
    content: |
    APT::Periodic::Update-Package-Lists "1";
    APT::Periodic::Unattended-Upgrade "1";
    owner: root
    group: root
    mode: '0644'
    - name: Reload SSH service
    systemd:
    name: ssh
    state: reloaded
    daemon_reload: yes
    handlers:
    - name: Restart Fail2Ban
    systemd:
    name: fail2ban
    state: restarted
    daemon_reload: yes
    EOF

    # Update the playbook with the correct username
    sed -i "s/ssh_allow_users: \"amos\"/ssh_allow_users: \"$NEW_USER\"/" "$PLAYBOOK_FILE"
    sed -i "s/ssh_allow_groups: \"amos\"/ssh_allow_groups: \"$NEW_USER\"/" "$PLAYBOOK_FILE"

    echo "βœ… Playbook created: $PLAYBOOK_FILE"

    # Run the hardening playbook
    echo "πŸ”’ Running hardening playbook..."
    ansible-playbook "$PLAYBOOK_FILE"

    # Check service status
    echo "πŸ” Checking service status..."
    systemctl status ssh --no-pager -l
    systemctl status fail2ban --no-pager -l
    systemctl status ufw --no-pager -l

    echo ""
    echo "πŸŽ‰ Server hardening complete!"
    echo ""
    echo "πŸ“‹ Summary:"
    echo " β€’ Created user: $NEW_USER"
    echo " β€’ Configured SSH hardening"
    echo " β€’ Enabled Fail2Ban"
    echo " β€’ Configured UFW firewall"
    echo " β€’ Enabled automatic security updates"
    echo ""
    echo "πŸ” Security recommendations:"
    echo " β€’ Test SSH access with: ssh $NEW_USER@$(hostname -I | awk '{print $1}')"
    echo " β€’ Disable root SSH login after confirming user access works"
    echo " β€’ Consider changing SSH port from default 22"
    echo " β€’ Review UFW rules: sudo ufw status"
    echo " β€’ Monitor logs: sudo journalctl -u fail2ban -f"
    echo ""
    echo "⚠️ IMPORTANT: Test SSH access before logging out!"