Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save johan--/b2523ef77dd92bd963417652c20fece9 to your computer and use it in GitHub Desktop.

Select an option

Save johan--/b2523ef77dd92bd963417652c20fece9 to your computer and use it in GitHub Desktop.

Revisions

  1. @rameerez rameerez revised this gist Oct 23, 2024. 1 changed file with 1 addition and 11 deletions.
    12 changes: 1 addition & 11 deletions kamal-production-server-setup.sh
    Original file line number Diff line number Diff line change
    @@ -300,15 +300,6 @@ curl -fsSL https://get.docker.com -o get-docker.sh
    sh get-docker.sh
    rm get-docker.sh

    # --- Docker User Namespace Setup ---
    print_message "${YELLOW}" "Setting up Docker user namespaces..."
    # Create dockremap user and group
    addgroup --system dockremap
    adduser --system --ingroup dockremap dockremap
    # Set up subuid and subgid mappings
    echo "dockremap:100000:65536" >> /etc/subuid
    echo "dockremap:100000:65536" >> /etc/subgid

    # --- Docker Configuration ---
    print_message "${YELLOW}" "Configuring Docker..."
    mkdir -p /etc/docker
    @@ -319,11 +310,10 @@ cat <<EOF > /etc/docker/daemon.json
    "max-size": "10m",
    "max-file": "3"
    },
    "icc": false,
    "icc": true,
    "live-restore": true,
    "userland-proxy": false,
    "no-new-privileges": true,
    "userns-remap": "default",
    "default-ulimits": {
    "nofile": {
    "Name": "nofile",
  2. @rameerez rameerez revised this gist Oct 23, 2024. 1 changed file with 3 additions and 3 deletions.
    6 changes: 3 additions & 3 deletions kamal-production-server-setup.sh
    Original file line number Diff line number Diff line change
    @@ -403,7 +403,7 @@ LogLevel VERBOSE
    LoginGraceTime 30
    PermitRootLogin prohibit-password
    StrictModes yes
    MaxAuthTries 3
    MaxAuthTries 10
    MaxSessions 5
    PubkeyAuthentication yes
    @@ -456,7 +456,7 @@ cat <<EOF > /etc/fail2ban/jail.local
    [DEFAULT]
    bantime = 3600
    findtime = 600
    maxretry = 6
    maxretry = 10
    banaction = ufw
    banaction_allports = ufw
    @@ -465,7 +465,7 @@ enabled = true
    port = ssh
    filter = sshd
    logpath = /var/log/auth.log
    maxretry = 6
    maxretry = 10
    bantime = 3600
    [docker]
  3. @rameerez rameerez revised this gist Oct 23, 2024. 1 changed file with 0 additions and 1 deletion.
    1 change: 0 additions & 1 deletion kamal-production-server-setup.sh
    Original file line number Diff line number Diff line change
    @@ -373,7 +373,6 @@ usermod -aG docker docker

    # --- SSH Configuration ---

    # --- SSH Configuration ---
    print_message "${YELLOW}" "Configuring SSH..."
    mkdir -p /home/docker/.ssh
    chown -R docker:docker /home/docker
  4. @rameerez rameerez revised this gist Oct 23, 2024. 1 changed file with 10 additions and 0 deletions.
    10 changes: 10 additions & 0 deletions kamal-production-server-setup.sh
    Original file line number Diff line number Diff line change
    @@ -371,12 +371,22 @@ print_message "${YELLOW}" "Creating docker user..."
    adduser --system --group --shell /bin/bash --home /home/docker --disabled-password docker
    usermod -aG docker docker

    # --- SSH Configuration ---

    # --- SSH Configuration ---
    print_message "${YELLOW}" "Configuring SSH..."
    mkdir -p /home/docker/.ssh
    chown -R docker:docker /home/docker
    chmod 755 /home/docker

    # Copy root's authorized keys to docker user if they exist
    if [ -f /root/.ssh/authorized_keys ]; then
    cp /root/.ssh/authorized_keys /home/docker/.ssh/authorized_keys
    chown -R docker:docker /home/docker/.ssh
    chmod 700 /home/docker/.ssh
    chmod 600 /home/docker/.ssh/authorized_keys
    fi

    cat <<EOF > /etc/ssh/sshd_config
    Include /etc/ssh/sshd_config.d/*.conf
  5. @rameerez rameerez revised this gist Oct 23, 2024. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions kamal-production-server-setup.sh
    Original file line number Diff line number Diff line change
    @@ -447,7 +447,7 @@ cat <<EOF > /etc/fail2ban/jail.local
    [DEFAULT]
    bantime = 3600
    findtime = 600
    maxretry = 3
    maxretry = 6
    banaction = ufw
    banaction_allports = ufw
    @@ -456,7 +456,7 @@ enabled = true
    port = ssh
    filter = sshd
    logpath = /var/log/auth.log
    maxretry = 3
    maxretry = 6
    bantime = 3600
    [docker]
  6. @rameerez rameerez revised this gist Oct 23, 2024. 1 changed file with 4 additions and 3 deletions.
    7 changes: 4 additions & 3 deletions kamal-production-server-setup.sh
    Original file line number Diff line number Diff line change
    @@ -87,7 +87,7 @@ verify_security_settings() {
    # Check kernel parameters
    local params=(
    "kernel.unprivileged_bpf_disabled=1"
    "net.ipv4.conf.all.log_martians=1"
    "net.ipv4.conf.all.log_martians=0"
    "net.ipv4.ip_forward=1"
    "fs.protected_hardlinks=1"
    "fs.protected_symlinks=1"
    @@ -273,13 +273,14 @@ fs.protected_symlinks = 1
    fs.suid_dumpable = 0
    # Additional network hardening
    net.ipv4.conf.all.log_martians = 1
    net.ipv4.conf.default.log_martians = 1
    net.ipv4.conf.all.log_martians = 0
    net.ipv4.conf.default.log_martians = 0
    net.ipv6.conf.all.accept_ra = 0
    net.ipv6.conf.default.accept_ra = 0
    EOF

    sysctl -p /etc/sysctl.d/99-security.conf
    sysctl --system # This loads all configs including the new one

    # Configure system limits
    cat <<EOF > /etc/security/limits.d/docker.conf
  7. @rameerez rameerez revised this gist Oct 23, 2024. 1 changed file with 5 additions and 2 deletions.
    7 changes: 5 additions & 2 deletions kamal-production-server-setup.sh
    Original file line number Diff line number Diff line change
    @@ -336,7 +336,6 @@ cat <<EOF > /etc/docker/daemon.json
    "experimental": false,
    "default-runtime": "runc",
    "storage-driver": "overlay2",
    "storage-opts": ["overlay2.override_kernel_check=true"],
    "metrics-addr": "127.0.0.1:9323",
    "builder": {
    "gc": {
    @@ -356,7 +355,11 @@ if ! docker info &>/dev/null; then
    fi

    systemctl enable docker
    systemctl restart docker
    systemctl restart docker || {
    print_error "Docker failed to start. Logs:"
    journalctl -u docker.service --no-pager | tail -n 50
    exit 1
    }

    # Verify Docker configuration
    print_message "${YELLOW}" "Verifying Docker configuration..."
  8. @rameerez rameerez revised this gist Oct 23, 2024. 1 changed file with 17 additions and 0 deletions.
    17 changes: 17 additions & 0 deletions kamal-production-server-setup.sh
    Original file line number Diff line number Diff line change
    @@ -299,6 +299,15 @@ curl -fsSL https://get.docker.com -o get-docker.sh
    sh get-docker.sh
    rm get-docker.sh

    # --- Docker User Namespace Setup ---
    print_message "${YELLOW}" "Setting up Docker user namespaces..."
    # Create dockremap user and group
    addgroup --system dockremap
    adduser --system --ingroup dockremap dockremap
    # Set up subuid and subgid mappings
    echo "dockremap:100000:65536" >> /etc/subuid
    echo "dockremap:100000:65536" >> /etc/subgid

    # --- Docker Configuration ---
    print_message "${YELLOW}" "Configuring Docker..."
    mkdir -p /etc/docker
    @@ -338,6 +347,14 @@ cat <<EOF > /etc/docker/daemon.json
    }
    EOF

    # After Docker daemon.json configuration
    print_message "${YELLOW}" "Testing Docker configuration..."
    if ! docker info &>/dev/null; then
    print_error "Docker failed to start. Checking configuration..."
    journalctl -u docker.service --no-pager | tail -n 50
    exit 1
    fi

    systemctl enable docker
    systemctl restart docker

  9. @rameerez rameerez revised this gist Oct 23, 2024. 1 changed file with 67 additions and 1 deletion.
    68 changes: 67 additions & 1 deletion kamal-production-server-setup.sh
    Original file line number Diff line number Diff line change
    @@ -110,6 +110,11 @@ verify_security_settings() {
    failed=1
    fi

    if [[ "$(stat -c %a /var/run/docker.sock)" != "660" ]]; then
    print_error "Docker socket has incorrect permissions"
    failed=1
    fi

    # Check services
    local services=(
    "docker"
    @@ -348,7 +353,8 @@ usermod -aG docker docker
    # --- SSH Configuration ---
    print_message "${YELLOW}" "Configuring SSH..."
    mkdir -p /home/docker/.ssh
    chmod 700 /home/docker/.ssh
    chown -R docker:docker /home/docker
    chmod 755 /home/docker

    cat <<EOF > /etc/ssh/sshd_config
    Include /etc/ssh/sshd_config.d/*.conf
    @@ -395,6 +401,8 @@ Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.
    MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com
    EOF

    systemctl reload ssh

    # --- Firewall Configuration ---
    print_message "${YELLOW}" "Configuring firewall..."
    ufw --force reset
    @@ -407,6 +415,13 @@ ufw --force enable

    # --- fail2ban Configuration ---
    print_message "${YELLOW}" "Configuring fail2ban..."

    cat <<EOF > /etc/fail2ban/filter.d/docker.conf
    [Definition]
    failregex = failed login attempt from <HOST>
    ignoreregex =
    EOF

    cat <<EOF > /etc/fail2ban/jail.local
    [DEFAULT]
    bantime = 3600
    @@ -444,6 +459,57 @@ else
    print_warning "Some security checks failed. Please review the warnings above."
    fi

    # Add logging configuration
    print_message "${YELLOW}" "Configuring system logging..."
    cat <<EOF > /etc/logrotate.d/docker-logs
    /var/lib/docker/containers/*/*.log {
    rotate 7
    daily
    compress
    size=100M
    missingok
    delaycompress
    copytruncate
    }
    EOF

    # Automated cleanup to prevent residual files
    print_message "${YELLOW}" "Setting up maintenance tasks..."
    cat <<EOF > /etc/cron.daily/docker-cleanup
    #!/bin/bash
    docker system prune -af --volumes
    docker builder prune -af --keep-storage=20GB
    EOF
    chmod +x /etc/cron.daily/docker-cleanup

    # Configure auditd
    cat <<EOF > /etc/audit/rules.d/audit.rules
    # Docker daemon configuration
    -w /usr/bin/dockerd -k docker
    -w /var/lib/docker -k docker
    -w /etc/docker -k docker
    -w /usr/lib/systemd/system/docker.service -k docker
    -w /etc/default/docker -k docker
    -w /etc/docker/daemon.json -k docker
    -w /usr/bin/docker -k docker-bin
    EOF

    # Reload audit rules
    auditctl -R /etc/audit/rules.d/audit.rules

    # Configure unattended-upgrades for automated security updates
    print_message "${YELLOW}" "Configuring automatic updates..."
    cat <<EOF > /etc/apt/apt.conf.d/50unattended-upgrades
    Unattended-Upgrade::Allowed-Origins {
    "\${distro_id}:\${distro_codename}-security";
    "\${distro_id}ESMApps:\${distro_codename}-apps-security";
    "\${distro_id}ESM:\${distro_codename}-infra-security";
    };
    Unattended-Upgrade::Remove-Unused-Dependencies "true";
    Unattended-Upgrade::Automatic-Reboot "false";
    EOF


    # --- Final Cleanup ---
    apt-get autoremove -y
    apt-get clean
  10. @rameerez rameerez revised this gist Oct 23, 2024. 1 changed file with 36 additions and 4 deletions.
    40 changes: 36 additions & 4 deletions kamal-production-server-setup.sh
    Original file line number Diff line number Diff line change
    @@ -126,6 +126,34 @@ verify_security_settings() {
    fi
    done

    # Check AIDE database
    if [ ! -f /var/lib/aide/aide.db ]; then
    print_error "AIDE database not initialized"
    failed=1
    fi

    # Check Chrony sync
    if ! chronyc tracking &>/dev/null; then
    print_error "Chrony is not syncing time"
    failed=1
    fi

    # Additional security checks
    if ! ufw status | grep -q "Status: active"; then
    print_error "UFW firewall is not active"
    failed=1
    fi

    if ! grep -q "PasswordAuthentication no" /etc/ssh/sshd_config; then
    print_error "SSH password authentication is not disabled"
    failed=1
    fi

    if ! apparmor_status | grep -q "apparmor module is loaded."; then
    print_error "AppArmor is not loaded"
    failed=1
    fi

    return $failed
    }

    @@ -181,9 +209,12 @@ systemctl stop systemd-timesyncd || true
    systemctl disable systemd-timesyncd || true
    apt-get remove -y systemd-timesyncd || true
    DEBIAN_FRONTEND=noninteractive apt-get install -y chrony
    systemctl enable chrony
    systemctl start chrony

    if systemctl -q is-enabled systemd-timesyncd 2>/dev/null; then
    systemctl disable systemd-timesyncd
    systemctl stop systemd-timesyncd
    fi
    systemctl enable chrony.service || true # use .service to avoid alias issues
    systemctl start chrony.service
    # --- System Hardening ---
    print_message "${YELLOW}" "Configuring system security..."

    @@ -192,7 +223,8 @@ systemctl enable apparmor
    systemctl start apparmor

    # Initialize AIDE
    aide --init
    aide --config=/etc/aide/aide.conf --init
    mv /var/lib/aide/aide.db.new /var/lib/aide/aide.db

    # Configure kernel parameters
    cat <<EOF > /etc/sysctl.d/99-security.conf
  11. @rameerez rameerez revised this gist Oct 23, 2024. 1 changed file with 183 additions and 124 deletions.
    307 changes: 183 additions & 124 deletions kamal-production-server-setup.sh
    Original file line number Diff line number Diff line change
    @@ -1,44 +1,156 @@
    #!/bin/bash

    # Production Docker Host Hardening Script
    # Production Docker Host Hardening Script v2
    # For Ubuntu Server 24.04 LTS (Noble)
    # Suitable for both Kamal deployment and builder hosts
    # For Ubuntu Server 24.04 LTS

    set -euo pipefail
    IFS=$'\n\t'

    # --- Aesthetics and Error Handling ---
    # --- Constants ---
    REQUIRED_OS="Ubuntu"
    REQUIRED_VERSION="24.04"
    MIN_RAM_MB=1024
    MIN_DISK_GB=20

    # --- Aesthetics ---
    GREEN='\033[0;32m'
    YELLOW='\033[1;33m'
    RED='\033[0;31m'
    ALIEN='\xF0\x9F\x91\xBD'
    NC='\033[0m'

    # --- Functions ---
    print_message() {
    local color=$1
    local message=$2
    echo -e "${color}${ALIEN} ${message}${NC}"
    }

    print_error() {
    print_message "${RED}" "ERROR: $1"
    }

    print_warning() {
    print_message "${YELLOW}" "WARNING: $1"
    }

    print_success() {
    print_message "${GREEN}" "SUCCESS: $1"
    }

    check_root() {
    if [[ $EUID -ne 0 ]]; then
    print_error "This script must be run as root"
    exit 1
    fi
    }

    check_os() {
    if ! command -v lsb_release >/dev/null 2>&1; then
    print_error "lsb_release command not found. Is this Ubuntu?"
    exit 1
    fi

    local os_name=$(lsb_release -is)
    local os_version=$(lsb_release -rs)

    if [[ "$os_name" != "$REQUIRED_OS" ]]; then
    print_error "This script requires $REQUIRED_OS (found $os_name)"
    exit 1
    fi

    if [[ "$os_version" != "$REQUIRED_VERSION" ]]; then
    print_error "This script requires Ubuntu $REQUIRED_VERSION (found $os_version)"
    exit 1
    fi
    }

    check_resources() {
    local total_ram_mb=$(free -m | awk '/^Mem:/{print $2}')
    local total_disk_gb=$(df -BG / | awk 'NR==2 {print $4}' | sed 's/G//')

    if (( total_ram_mb < MIN_RAM_MB )); then
    print_error "Insufficient RAM. Required: ${MIN_RAM_MB}MB, Found: ${total_ram_mb}MB"
    exit 1
    fi

    if (( total_disk_gb < MIN_DISK_GB )); then
    print_error "Insufficient disk space. Required: ${MIN_DISK_GB}GB, Found: ${total_disk_gb}GB"
    exit 1
    fi
    }

    verify_security_settings() {
    local failed=0

    # Check kernel parameters
    local params=(
    "kernel.unprivileged_bpf_disabled=1"
    "net.ipv4.conf.all.log_martians=1"
    "net.ipv4.ip_forward=1"
    "fs.protected_hardlinks=1"
    "fs.protected_symlinks=1"
    )

    for param in "${params[@]}"; do
    local name=${param%=*}
    local expected=${param#*=}
    local actual=$(sysctl -n "$name" 2>/dev/null || echo "NOT_FOUND")

    if [[ "$actual" != "$expected" ]]; then
    print_error "Kernel parameter $name = $actual (expected $expected)"
    failed=1
    fi
    done

    # Check Docker settings
    if ! docker info 2>/dev/null | grep -q "Cgroup Driver: systemd"; then
    print_error "Docker is not using systemd cgroup driver"
    failed=1
    fi

    # Check services
    local services=(
    "docker"
    "fail2ban"
    "ufw"
    "auditd"
    "chrony"
    )

    for service in "${services[@]}"; do
    if ! systemctl is-active --quiet "$service"; then
    print_error "Service $service is not running"
    failed=1
    fi
    done

    return $failed
    }

    handle_error() {
    local line_number=$1
    print_message "${RED}" "Error on line ${line_number}"
    print_error "Script failed on line ${line_number}"
    print_error "Please check the logs above for more information"
    exit 1
    }

    # Set up error handling
    trap 'handle_error ${LINENO}' ERR

    # Check if script is run as root
    if [[ $EUID -ne 0 ]]; then
    print_message "${RED}" "This script must be run as root"
    exit 1
    fi
    # --- Pre-flight Checks ---
    print_message "${YELLOW}" "Performing pre-flight checks..."
    check_root
    check_os
    check_resources

    # --- System Updates and Essential Packages ---
    # --- System Updates ---
    print_message "${YELLOW}" "Updating system packages..."
    apt-get update
    DEBIAN_FRONTEND=noninteractive apt-get upgrade -y

    # --- Essential Packages ---
    print_message "${YELLOW}" "Installing essential packages..."
    DEBIAN_FRONTEND=noninteractive apt-get install -y \
    ufw \
    @@ -55,50 +167,34 @@ DEBIAN_FRONTEND=noninteractive apt-get install -y \
    audispd-plugins \
    unattended-upgrades \
    acl \
    openssh-server \
    apparmor \
    apparmor-utils \
    chrony \
    aide \
    rkhunter \
    logwatch \
    git
    git \
    python3-pyinotify

    # --- Time Synchronization ---
    print_message "${YELLOW}" "Configuring time synchronization..."
    systemctl stop systemd-timesyncd || true
    systemctl disable systemd-timesyncd || true
    apt-get remove -y systemd-timesyncd || true
    DEBIAN_FRONTEND=noninteractive apt-get install -y chrony
    systemctl enable chrony
    systemctl start chrony

    # --- System Hardening ---
    print_message "${YELLOW}" "Configuring system security..."

    # AppArmor
    # Configure AppArmor
    systemctl enable apparmor
    systemctl start apparmor

    # AIDE initialization
    aideinit

    # Configure chrony for secure time sync
    cat <<EOF > /etc/chrony/chrony.conf
    pool pool.ntp.org iburst maxsources 4
    keyfile /etc/chrony/chrony.keys
    driftfile /var/lib/chrony/chrony.drift
    logdir /var/log/chrony
    maxupdateskew 100.0
    rtcsync
    makestep 1 3
    leapsectz right/UTC
    EOF

    # Configure system limits
    cat <<EOF > /etc/security/limits.d/docker.conf
    * soft nproc 10000
    * hard nproc 10000
    * soft nofile 1048576
    * hard nofile 1048576
    * soft core 0
    * hard core 0
    * soft stack 8192
    * hard stack 8192
    EOF
    # Initialize AIDE
    aide --init

    # Kernel hardening
    # Configure kernel parameters
    cat <<EOF > /etc/sysctl.d/99-security.conf
    # Network security
    net.ipv4.conf.all.send_redirects = 0
    @@ -148,12 +244,25 @@ EOF

    sysctl -p /etc/sysctl.d/99-security.conf

    # --- Docker Installation and Configuration ---
    # Configure system limits
    cat <<EOF > /etc/security/limits.d/docker.conf
    * soft nproc 10000
    * hard nproc 10000
    * soft nofile 1048576
    * hard nofile 1048576
    * soft core 0
    * hard core 0
    * soft stack 8192
    * hard stack 8192
    EOF

    # --- Docker Installation ---
    print_message "${YELLOW}" "Installing Docker..."
    curl -fsSL https://get.docker.com -o get-docker.sh
    sh get-docker.sh
    rm get-docker.sh

    # --- Docker Configuration ---
    print_message "${YELLOW}" "Configuring Docker..."
    mkdir -p /etc/docker
    cat <<EOF > /etc/docker/daemon.json
    @@ -180,9 +289,6 @@ cat <<EOF > /etc/docker/daemon.json
    },
    "experimental": false,
    "default-runtime": "runc",
    "seccomp-profile": "/etc/docker/seccomp.json",
    "selinux-enabled": true,
    "userns-remap": "default",
    "storage-driver": "overlay2",
    "storage-opts": ["overlay2.override_kernel_check=true"],
    "metrics-addr": "127.0.0.1:9323",
    @@ -195,12 +301,19 @@ cat <<EOF > /etc/docker/daemon.json
    }
    EOF

    systemctl enable docker
    systemctl restart docker

    # Verify Docker configuration
    print_message "${YELLOW}" "Verifying Docker configuration..."
    docker info | grep -E "Cgroup Driver|Storage Driver|Logging Driver"

    # --- User Setup ---
    print_message "${YELLOW}" "Creating docker user..."
    adduser --system --group --shell /bin/bash --home /home/docker --disabled-password docker
    usermod -aG docker docker

    # --- SSH Hardening ---
    # --- SSH Configuration ---
    print_message "${YELLOW}" "Configuring SSH..."
    mkdir -p /home/docker/.ssh
    chmod 700 /home/docker/.ssh
    @@ -248,8 +361,6 @@ AllowUsers docker root
    KexAlgorithms curve25519-sha256@libssh.org,ecdh-sha2-nistp521,ecdh-sha2-nistp384,ecdh-sha2-nistp256
    Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr
    MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com
    Subsystem sftp internal-sftp
    EOF

    # --- Firewall Configuration ---
    @@ -262,7 +373,7 @@ ufw allow http
    ufw allow https
    ufw --force enable

    # --- Fail2ban Configuration ---
    # --- fail2ban Configuration ---
    print_message "${YELLOW}" "Configuring fail2ban..."
    cat <<EOF > /etc/fail2ban/jail.local
    [DEFAULT]
    @@ -286,89 +397,37 @@ filter = docker
    logpath = /var/log/auth.log
    maxretry = 5
    bantime = 3600
    [docker-api]
    enabled = true
    port = 2375,2376
    filter = docker-api
    logpath = /var/log/docker.log
    maxretry = 3
    bantime = 3600
    EOF

    # Create custom Docker fail2ban filter
    cat <<EOF > /etc/fail2ban/filter.d/docker.conf
    [Definition]
    failregex = failed login attempt from <HOST>
    ignoreregex =
    EOF

    # --- Logging and Monitoring ---
    print_message "${YELLOW}" "Configuring logging and monitoring..."

    # Docker log rotation
    cat <<EOF > /etc/logrotate.d/docker
    /var/lib/docker/containers/*/*.log {
    rotate 7
    daily
    compress
    missingok
    delaycompress
    copytruncate
    size 100M
    }
    EOF

    # Configure auditd
    cat <<EOF > /etc/audit/rules.d/docker.rules
    # Docker daemon configuration
    -w /usr/bin/dockerd -k docker
    -w /var/lib/docker -k docker
    -w /etc/docker -k docker
    -w /usr/lib/systemd/system/docker.service -k docker
    -w /etc/systemd/system/docker.service -k docker
    -w /usr/lib/systemd/system/docker.socket -k docker
    -w /etc/default/docker -k docker
    -w /etc/docker/daemon.json -k docker
    EOF

    # --- Automatic Updates ---
    print_message "${YELLOW}" "Configuring automatic updates..."
    cat <<EOF > /etc/apt/apt.conf.d/50unattended-upgrades
    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::Remove-Unused-Dependencies "true";
    Unattended-Upgrade::Remove-New-Unused-Dependencies "true";
    Unattended-Upgrade::AutoFixInterruptedDpkg "true";
    Unattended-Upgrade::MinimalSteps "true";
    Unattended-Upgrade::InstallOnShutdown "false";
    EOF

    # --- System Maintenance ---
    print_message "${YELLOW}" "Setting up maintenance tasks..."
    cat <<EOF > /etc/cron.daily/docker-cleanup
    #!/bin/bash
    docker system prune -af --volumes
    docker builder prune -af --keep-storage=20GB
    EOF
    chmod +x /etc/cron.daily/docker-cleanup

    # Enable and start services
    systemctl enable docker fail2ban auditd chronyd
    systemctl restart docker fail2ban auditd chronyd
    # --- Enable and Start Services ---
    print_message "${YELLOW}" "Enabling services..."
    systemctl enable docker fail2ban auditd chrony
    systemctl restart docker fail2ban auditd chrony

    # --- Verify Setup ---
    print_message "${YELLOW}" "Verifying security settings..."
    if verify_security_settings; then
    print_success "Security verification passed"
    else
    print_warning "Some security checks failed. Please review the warnings above."
    fi

    # Final cleanup
    # --- Final Cleanup ---
    apt-get autoremove -y
    apt-get clean

    print_message "${GREEN}" "Setup complete! System hardening successful."
    print_success "Setup complete! System hardening successful."
    print_message "${YELLOW}" "Important next steps:"
    print_message "${YELLOW}" "1. Add your SSH public key to /home/docker/.ssh/authorized_keys"
    print_message "${YELLOW}" "2. Configure your deployment tools (Kamal, etc.)"
    print_message "${YELLOW}" "3. REBOOT THE SYSTEM to apply all security settings"
    print_message "${YELLOW}" "4. Review logs in /var/log/ regularly"
    print_message "${YELLOW}" "5. Set up external monitoring and alerting"
    print_message "${YELLOW}" "5. Set up external monitoring and alerting"

    # Additional verification info
    print_message "${GREEN}" "System Information:"
    echo "Docker Version: $(docker --version)"
    echo "Kernel Version: $(uname -r)"
    echo "AppArmor Status: $(aa-status --enabled && echo 'Enabled' || echo 'Disabled')"
    echo "UFW Status: $(ufw status | grep Status)"
    echo "fail2ban Status: $(fail2ban-client status | grep "Number of jail:")"
  12. @rameerez rameerez revised this gist Oct 23, 2024. 1 changed file with 210 additions and 132 deletions.
    342 changes: 210 additions & 132 deletions kamal-production-server-setup.sh
    Original file line number Diff line number Diff line change
    @@ -1,51 +1,44 @@
    #!/bin/bash

    # Production Kamal Docker Host Setup Script
    # Sets up a secure, production-ready Docker host on Ubuntu Server 24.04 LTS
    # Ready for Kamal 2.x deployments
    # Production Docker Host Hardening Script
    # Suitable for both Kamal deployment and builder hosts
    # For Ubuntu Server 24.04 LTS

    set -euo pipefail
    IFS=$'\n\t'

    # --- AESTHETICS ---

    # Define color codes for echo messages
    # --- Aesthetics and Error Handling ---
    GREEN='\033[0;32m'
    YELLOW='\033[1;33m'
    RED='\033[0;31m'

    # Define the escape sequence for the alien emoji (U+1F47D)
    ALIEN='\xF0\x9F\x91\xBD'

    # Define the variable for resetting the color back to the default
    NC='\033[0m'

    # Function to print colorized output with alien emoji
    print_message() {
    local color=$1
    local message=$2
    echo -e "${color}${ALIEN} ${message}${NC}"
    }

    # Function to handle errors
    handle_error() {
    print_message "${RED}" "An error occurred. Exiting..."
    local line_number=$1
    print_message "${RED}" "Error on line ${line_number}"
    exit 1
    }

    # Set up error handling
    trap 'handle_error' ERR
    trap 'handle_error ${LINENO}' ERR

    # Check if script is run as root
    if [[ $EUID -ne 0 ]]; then
    print_message "${RED}" "This script must be run as root"
    exit 1
    fi

    # System Updates
    # --- System Updates and Essential Packages ---
    print_message "${YELLOW}" "Updating system packages..."
    apt-get update && DEBIAN_FRONTEND=noninteractive apt-get upgrade -y
    apt-get update
    DEBIAN_FRONTEND=noninteractive apt-get upgrade -y

    # Install essential packages
    print_message "${YELLOW}" "Installing essential packages..."
    DEBIAN_FRONTEND=noninteractive apt-get install -y \
    ufw \
    @@ -61,48 +54,106 @@ DEBIAN_FRONTEND=noninteractive apt-get install -y \
    auditd \
    audispd-plugins \
    unattended-upgrades \
    acl
    acl \
    openssh-server \
    apparmor \
    apparmor-utils \
    chrony \
    aide \
    rkhunter \
    logwatch \
    git

    # --- System Hardening ---
    print_message "${YELLOW}" "Configuring system security..."

    # AppArmor
    systemctl enable apparmor
    systemctl start apparmor

    # AIDE initialization
    aideinit

    # Configure chrony for secure time sync
    cat <<EOF > /etc/chrony/chrony.conf
    pool pool.ntp.org iburst maxsources 4
    keyfile /etc/chrony/chrony.keys
    driftfile /var/lib/chrony/chrony.drift
    logdir /var/log/chrony
    maxupdateskew 100.0
    rtcsync
    makestep 1 3
    leapsectz right/UTC
    EOF

    # Configure UFW
    print_message "${YELLOW}" "Configuring firewall..."
    ufw --force reset
    ufw default deny incoming
    ufw default allow outgoing
    ufw allow ssh
    ufw allow http
    ufw allow https
    echo "y" | ufw enable
    # Configure system limits
    cat <<EOF > /etc/security/limits.d/docker.conf
    * soft nproc 10000
    * hard nproc 10000
    * soft nofile 1048576
    * hard nofile 1048576
    * soft core 0
    * hard core 0
    * soft stack 8192
    * hard stack 8192
    EOF

    # Configure fail2ban with aggressive settings
    print_message "${YELLOW}" "Configuring fail2ban..."
    cat <<EOF > /etc/fail2ban/jail.local
    [sshd]
    enabled = true
    port = ssh
    filter = sshd
    logpath = /var/log/auth.log
    maxretry = 3
    bantime = 3600
    findtime = 600
    # Kernel hardening
    cat <<EOF > /etc/sysctl.d/99-security.conf
    # Network security
    net.ipv4.conf.all.send_redirects = 0
    net.ipv4.conf.default.send_redirects = 0
    net.ipv4.conf.all.accept_redirects = 0
    net.ipv4.conf.default.accept_redirects = 0
    net.ipv4.conf.all.rp_filter = 1
    net.ipv4.conf.default.rp_filter = 1
    net.ipv4.icmp_echo_ignore_broadcasts = 1
    net.ipv4.icmp_ignore_bogus_error_responses = 1
    net.ipv4.conf.all.accept_source_route = 0
    net.ipv4.conf.default.accept_source_route = 0
    net.ipv6.conf.all.accept_source_route = 0
    net.ipv6.conf.default.accept_source_route = 0
    net.ipv4.tcp_syncookies = 1
    net.ipv4.tcp_max_syn_backlog = 2048
    net.ipv4.tcp_synack_retries = 2
    net.ipv4.tcp_syn_retries = 5
    [docker-login]
    enabled = true
    port = http,https
    filter = apache-auth
    logpath = /var/log/auth.log
    maxretry = 3
    bantime = 3600
    # Docker needs IPv4 forwarding
    net.ipv4.ip_forward = 1
    # System limits
    fs.file-max = 1048576
    kernel.pid_max = 65536
    net.ipv4.ip_local_port_range = 1024 65000
    net.ipv4.tcp_tw_reuse = 1
    vm.max_map_count = 262144
    kernel.kptr_restrict = 2
    kernel.dmesg_restrict = 1
    kernel.perf_event_paranoid = 3
    kernel.unprivileged_bpf_disabled = 1
    net.core.bpf_jit_harden = 2
    kernel.yama.ptrace_scope = 2
    # File system hardening
    fs.protected_hardlinks = 1
    fs.protected_symlinks = 1
    fs.suid_dumpable = 0
    # Additional network hardening
    net.ipv4.conf.all.log_martians = 1
    net.ipv4.conf.default.log_martians = 1
    net.ipv6.conf.all.accept_ra = 0
    net.ipv6.conf.default.accept_ra = 0
    EOF
    systemctl enable fail2ban
    systemctl restart fail2ban

    # Install Docker
    sysctl -p /etc/sysctl.d/99-security.conf

    # --- Docker Installation and Configuration ---
    print_message "${YELLOW}" "Installing Docker..."
    curl -fsSL https://get.docker.com -o get-docker.sh
    sh get-docker.sh
    rm get-docker.sh

    # Docker Configuration
    print_message "${YELLOW}" "Configuring Docker..."
    mkdir -p /etc/docker
    cat <<EOF > /etc/docker/daemon.json
    @@ -116,76 +167,74 @@ cat <<EOF > /etc/docker/daemon.json
    "live-restore": true,
    "userland-proxy": false,
    "no-new-privileges": true,
    "userns-remap": "default",
    "default-ulimits": {
    "nofile": {
    "Name": "nofile",
    "Hard": 64000,
    "Soft": 64000
    }
    },
    "metrics-addr": "0.0.0.0:9323",
    "experimental": true,
    "features": {
    "buildkit": true
    },
    "experimental": false,
    "default-runtime": "runc",
    "seccomp-profile": "/etc/docker/seccomp.json",
    "selinux-enabled": true,
    "userns-remap": "default",
    "storage-driver": "overlay2",
    "storage-opts": ["overlay2.override_kernel_check=true"],
    "metrics-addr": "127.0.0.1:9323",
    "builder": {
    "gc": {
    "enabled": true,
    "defaultKeepStorage": "20GB"
    }
    }
    }
    EOF

    systemctl enable docker
    systemctl restart docker

    # Set up Docker user
    # --- User Setup ---
    print_message "${YELLOW}" "Creating docker user..."
    adduser --system --group --shell /bin/bash --home /home/docker --disabled-password docker
    usermod -aG docker docker

    # Set up SSH for docker user
    # --- SSH Hardening ---
    print_message "${YELLOW}" "Configuring SSH..."
    mkdir -p /home/docker/.ssh
    chmod 700 /home/docker/.ssh

    if [ -f /root/.ssh/authorized_keys ]; then
    cp /root/.ssh/authorized_keys /home/docker/.ssh/
    chmod 600 /home/docker/.ssh/authorized_keys
    chown -R docker:docker /home/docker/.ssh
    else
    print_message "${RED}" "No SSH key found in /root/.ssh/authorized_keys"
    exit 1
    fi

    # SSH Hardening
    cat <<EOF > /etc/ssh/sshd_config
    Include /etc/ssh/sshd_config.d/*.conf
    Port 22
    AddressFamily inet
    Protocol 2
    HostKey /etc/ssh/ssh_host_rsa_key
    HostKey /etc/ssh/ssh_host_ecdsa_key
    HostKey /etc/ssh/ssh_host_ed25519_key
    HostKey /etc/ssh/ssh_host_ecdsa_key
    HostKey /etc/ssh/ssh_host_rsa_key
    SyslogFacility AUTH
    LogLevel VERBOSE
    LoginGraceTime 60
    LoginGraceTime 30
    PermitRootLogin prohibit-password
    StrictModes yes
    MaxAuthTries 6
    MaxSessions 10
    MaxAuthTries 3
    MaxSessions 5
    PubkeyAuthentication yes
    HostbasedAuthentication no
    IgnoreRhosts yes
    PasswordAuthentication no
    PermitEmptyPasswords no
    ChallengeResponseAuthentication no
    UsePAM yes
    AllowAgentForwarding no
    AllowTcpForwarding no
    AllowTcpForwarding yes # Required for Docker forwarding
    X11Forwarding no
    PermitTTY yes
    PrintMotd no
    @@ -199,63 +248,66 @@ AllowUsers docker root
    KexAlgorithms curve25519-sha256@libssh.org,ecdh-sha2-nistp521,ecdh-sha2-nistp384,ecdh-sha2-nistp256
    Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr
    MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com
    Subsystem sftp internal-sftp
    EOF

    # Ensure proper ownership and permissions of the entire /home/docker directory
    chown -R docker:docker /home/docker
    chmod 755 /home/docker

    # Restart SSH service - now using correct service name
    if systemctl is-active --quiet ssh; then
    systemctl restart ssh
    elif systemctl is-active --quiet sshd; then
    systemctl restart sshd
    else
    print_message "${RED}" "SSH service not found. Please check your SSH installation."
    exit 1
    fi
    # --- Firewall Configuration ---
    print_message "${YELLOW}" "Configuring firewall..."
    ufw --force reset
    ufw default deny incoming
    ufw default allow outgoing
    ufw allow ssh
    ufw allow http
    ufw allow https
    ufw --force enable

    # Kamal-specific sudo permissions
    print_message "${YELLOW}" "Configuring sudo for Kamal..."
    cat <<EOF > /etc/sudoers.d/docker-user
    docker ALL=(ALL) NOPASSWD: /usr/sbin/service docker restart
    docker ALL=(ALL) NOPASSWD: /bin/systemctl restart docker
    EOF
    chmod 440 /etc/sudoers.d/docker-user
    # --- Fail2ban Configuration ---
    print_message "${YELLOW}" "Configuring fail2ban..."
    cat <<EOF > /etc/fail2ban/jail.local
    [DEFAULT]
    bantime = 3600
    findtime = 600
    maxretry = 3
    banaction = ufw
    banaction_allports = ufw
    # System hardening
    print_message "${YELLOW}" "Hardening system..."
    cat <<EOF >> /etc/sysctl.conf
    # Network security
    net.ipv4.conf.all.send_redirects = 0
    net.ipv4.conf.default.send_redirects = 0
    net.ipv4.conf.all.accept_redirects = 0
    net.ipv4.conf.default.accept_redirects = 0
    net.ipv4.conf.all.rp_filter = 1
    net.ipv4.conf.default.rp_filter = 1
    net.ipv4.icmp_echo_ignore_broadcasts = 1
    net.ipv4.icmp_ignore_bogus_error_responses = 1
    net.ipv4.conf.all.accept_source_route = 0
    net.ipv4.conf.default.accept_source_route = 0
    net.ipv6.conf.all.accept_source_route = 0
    net.ipv6.conf.default.accept_source_route = 0
    net.ipv4.tcp_syncookies = 1
    [sshd]
    enabled = true
    port = ssh
    filter = sshd
    logpath = /var/log/auth.log
    maxretry = 3
    bantime = 3600
    # Docker needs IPv4 forwarding
    net.ipv4.ip_forward = 1
    [docker]
    enabled = true
    filter = docker
    logpath = /var/log/auth.log
    maxretry = 5
    bantime = 3600
    # System limits for Kamal/Docker
    fs.file-max = 65535
    kernel.pid_max = 65536
    net.ipv4.ip_local_port_range = 1024 65000
    net.ipv4.tcp_tw_reuse = 1
    vm.max_map_count = 262144
    [docker-api]
    enabled = true
    port = 2375,2376
    filter = docker-api
    logpath = /var/log/docker.log
    maxretry = 3
    bantime = 3600
    EOF

    sysctl -p
    # Create custom Docker fail2ban filter
    cat <<EOF > /etc/fail2ban/filter.d/docker.conf
    [Definition]
    failregex = failed login attempt from <HOST>
    ignoreregex =
    EOF

    # --- Logging and Monitoring ---
    print_message "${YELLOW}" "Configuring logging and monitoring..."

    # Log rotation for Docker
    cat <<EOF > /etc/logrotate.d/docker-logs
    # Docker log rotation
    cat <<EOF > /etc/logrotate.d/docker
    /var/lib/docker/containers/*/*.log {
    rotate 7
    daily
    @@ -267,11 +319,21 @@ cat <<EOF > /etc/logrotate.d/docker-logs
    }
    EOF

    # Enable security services
    systemctl enable sysstat auditd
    systemctl start sysstat auditd
    # Configure auditd
    cat <<EOF > /etc/audit/rules.d/docker.rules
    # Docker daemon configuration
    -w /usr/bin/dockerd -k docker
    -w /var/lib/docker -k docker
    -w /etc/docker -k docker
    -w /usr/lib/systemd/system/docker.service -k docker
    -w /etc/systemd/system/docker.service -k docker
    -w /usr/lib/systemd/system/docker.socket -k docker
    -w /etc/default/docker -k docker
    -w /etc/docker/daemon.json -k docker
    EOF

    # Configure unattended upgrades
    # --- Automatic Updates ---
    print_message "${YELLOW}" "Configuring automatic updates..."
    cat <<EOF > /etc/apt/apt.conf.d/50unattended-upgrades
    Unattended-Upgrade::Allowed-Origins {
    "\${distro_id}:\${distro_codename}";
    @@ -283,14 +345,30 @@ Unattended-Upgrade::Remove-Unused-Dependencies "true";
    Unattended-Upgrade::Remove-New-Unused-Dependencies "true";
    Unattended-Upgrade::AutoFixInterruptedDpkg "true";
    Unattended-Upgrade::MinimalSteps "true";
    Unattended-Upgrade::InstallOnShutdown "false";
    EOF

    # Clean up
    # --- System Maintenance ---
    print_message "${YELLOW}" "Setting up maintenance tasks..."
    cat <<EOF > /etc/cron.daily/docker-cleanup
    #!/bin/bash
    docker system prune -af --volumes
    docker builder prune -af --keep-storage=20GB
    EOF
    chmod +x /etc/cron.daily/docker-cleanup

    # Enable and start services
    systemctl enable docker fail2ban auditd chronyd
    systemctl restart docker fail2ban auditd chronyd

    # Final cleanup
    apt-get autoremove -y
    apt-get clean

    print_message "${GREEN}" "Setup complete! Please reboot the system."
    print_message "${GREEN}" "Setup complete! System hardening successful."
    print_message "${YELLOW}" "Important next steps:"
    print_message "${YELLOW}" "1. Configure your load balancer to point to this host"
    print_message "${YELLOW}" "2. Set up your .kamal/secrets file on your deployment machine"
    print_message "${YELLOW}" "3. Run 'kamal deploy' to deploy your application"
    print_message "${YELLOW}" "1. Add your SSH public key to /home/docker/.ssh/authorized_keys"
    print_message "${YELLOW}" "2. Configure your deployment tools (Kamal, etc.)"
    print_message "${YELLOW}" "3. REBOOT THE SYSTEM to apply all security settings"
    print_message "${YELLOW}" "4. Review logs in /var/log/ regularly"
    print_message "${YELLOW}" "5. Set up external monitoring and alerting"
  13. @rameerez rameerez revised this gist Oct 22, 2024. 1 changed file with 10 additions and 7 deletions.
    17 changes: 10 additions & 7 deletions kamal-production-server-setup.sh
    Original file line number Diff line number Diff line change
    @@ -168,14 +168,13 @@ HostKey /etc/ssh/ssh_host_ed25519_key
    SyslogFacility AUTH
    LogLevel VERBOSE
    LoginGraceTime 20
    PermitRootLogin no
    LoginGraceTime 60
    PermitRootLogin prohibit-password
    StrictModes yes
    MaxAuthTries 3
    MaxSessions 2
    MaxAuthTries 6
    MaxSessions 10
    PubkeyAuthentication yes
    AuthenticationMethods publickey
    HostbasedAuthentication no
    IgnoreRhosts yes
    @@ -184,7 +183,7 @@ PasswordAuthentication no
    PermitEmptyPasswords no
    ChallengeResponseAuthentication no
    UsePAM no
    UsePAM yes
    AllowAgentForwarding no
    AllowTcpForwarding no
    X11Forwarding no
    @@ -195,13 +194,17 @@ ClientAliveInterval 300
    ClientAliveCountMax 2
    TCPKeepAlive no
    AllowUsers docker
    AllowUsers docker root
    KexAlgorithms curve25519-sha256@libssh.org,ecdh-sha2-nistp521,ecdh-sha2-nistp384,ecdh-sha2-nistp256
    Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr
    MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com
    EOF

    # Ensure proper ownership and permissions of the entire /home/docker directory
    chown -R docker:docker /home/docker
    chmod 755 /home/docker

    # Restart SSH service - now using correct service name
    if systemctl is-active --quiet ssh; then
    systemctl restart ssh
  14. @rameerez rameerez revised this gist Oct 22, 2024. 1 changed file with 9 additions and 1 deletion.
    10 changes: 9 additions & 1 deletion kamal-production-server-setup.sh
    Original file line number Diff line number Diff line change
    @@ -202,7 +202,15 @@ Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.
    MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com
    EOF

    systemctl restart sshd
    # Restart SSH service - now using correct service name
    if systemctl is-active --quiet ssh; then
    systemctl restart ssh
    elif systemctl is-active --quiet sshd; then
    systemctl restart sshd
    else
    print_message "${RED}" "SSH service not found. Please check your SSH installation."
    exit 1
    fi

    # Kamal-specific sudo permissions
    print_message "${YELLOW}" "Configuring sudo for Kamal..."
  15. @rameerez rameerez revised this gist Oct 22, 2024. 1 changed file with 1 addition and 2 deletions.
    3 changes: 1 addition & 2 deletions kamal-production-server-setup.sh
    Original file line number Diff line number Diff line change
    @@ -61,8 +61,7 @@ DEBIAN_FRONTEND=noninteractive apt-get install -y \
    auditd \
    audispd-plugins \
    unattended-upgrades \
    acl \
    netcat
    acl

    # Configure UFW
    print_message "${YELLOW}" "Configuring firewall..."
  16. @rameerez rameerez created this gist Oct 22, 2024.
    286 changes: 286 additions & 0 deletions kamal-production-server-setup.sh
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,286 @@
    #!/bin/bash

    # Production Kamal Docker Host Setup Script
    # Sets up a secure, production-ready Docker host on Ubuntu Server 24.04 LTS
    # Ready for Kamal 2.x deployments

    set -euo pipefail

    # --- AESTHETICS ---

    # Define color codes for echo messages
    GREEN='\033[0;32m'
    YELLOW='\033[1;33m'
    RED='\033[0;31m'

    # Define the escape sequence for the alien emoji (U+1F47D)
    ALIEN='\xF0\x9F\x91\xBD'

    # Define the variable for resetting the color back to the default
    NC='\033[0m'

    # Function to print colorized output with alien emoji
    print_message() {
    local color=$1
    local message=$2
    echo -e "${color}${ALIEN} ${message}${NC}"
    }

    # Function to handle errors
    handle_error() {
    print_message "${RED}" "An error occurred. Exiting..."
    exit 1
    }

    # Set up error handling
    trap 'handle_error' ERR

    # Check if script is run as root
    if [[ $EUID -ne 0 ]]; then
    print_message "${RED}" "This script must be run as root"
    exit 1
    fi

    # System Updates
    print_message "${YELLOW}" "Updating system packages..."
    apt-get update && DEBIAN_FRONTEND=noninteractive apt-get upgrade -y

    # Install essential packages
    print_message "${YELLOW}" "Installing essential packages..."
    DEBIAN_FRONTEND=noninteractive apt-get install -y \
    ufw \
    fail2ban \
    curl \
    wget \
    gnupg \
    lsb-release \
    ca-certificates \
    apt-transport-https \
    software-properties-common \
    sysstat \
    auditd \
    audispd-plugins \
    unattended-upgrades \
    acl \
    netcat

    # Configure UFW
    print_message "${YELLOW}" "Configuring firewall..."
    ufw --force reset
    ufw default deny incoming
    ufw default allow outgoing
    ufw allow ssh
    ufw allow http
    ufw allow https
    echo "y" | ufw enable

    # Configure fail2ban with aggressive settings
    print_message "${YELLOW}" "Configuring fail2ban..."
    cat <<EOF > /etc/fail2ban/jail.local
    [sshd]
    enabled = true
    port = ssh
    filter = sshd
    logpath = /var/log/auth.log
    maxretry = 3
    bantime = 3600
    findtime = 600
    [docker-login]
    enabled = true
    port = http,https
    filter = apache-auth
    logpath = /var/log/auth.log
    maxretry = 3
    bantime = 3600
    EOF
    systemctl enable fail2ban
    systemctl restart fail2ban

    # Install Docker
    print_message "${YELLOW}" "Installing Docker..."
    curl -fsSL https://get.docker.com -o get-docker.sh
    sh get-docker.sh
    rm get-docker.sh

    # Docker Configuration
    print_message "${YELLOW}" "Configuring Docker..."
    mkdir -p /etc/docker
    cat <<EOF > /etc/docker/daemon.json
    {
    "log-driver": "json-file",
    "log-opts": {
    "max-size": "10m",
    "max-file": "3"
    },
    "icc": false,
    "live-restore": true,
    "userland-proxy": false,
    "no-new-privileges": true,
    "default-ulimits": {
    "nofile": {
    "Name": "nofile",
    "Hard": 64000,
    "Soft": 64000
    }
    },
    "metrics-addr": "0.0.0.0:9323",
    "experimental": true,
    "features": {
    "buildkit": true
    }
    }
    EOF

    systemctl enable docker
    systemctl restart docker

    # Set up Docker user
    print_message "${YELLOW}" "Creating docker user..."
    adduser --system --group --shell /bin/bash --home /home/docker --disabled-password docker
    usermod -aG docker docker

    # Set up SSH for docker user
    print_message "${YELLOW}" "Configuring SSH..."
    mkdir -p /home/docker/.ssh
    chmod 700 /home/docker/.ssh

    if [ -f /root/.ssh/authorized_keys ]; then
    cp /root/.ssh/authorized_keys /home/docker/.ssh/
    chmod 600 /home/docker/.ssh/authorized_keys
    chown -R docker:docker /home/docker/.ssh
    else
    print_message "${RED}" "No SSH key found in /root/.ssh/authorized_keys"
    exit 1
    fi

    # SSH Hardening
    cat <<EOF > /etc/ssh/sshd_config
    Include /etc/ssh/sshd_config.d/*.conf
    Port 22
    AddressFamily inet
    Protocol 2
    HostKey /etc/ssh/ssh_host_rsa_key
    HostKey /etc/ssh/ssh_host_ecdsa_key
    HostKey /etc/ssh/ssh_host_ed25519_key
    SyslogFacility AUTH
    LogLevel VERBOSE
    LoginGraceTime 20
    PermitRootLogin no
    StrictModes yes
    MaxAuthTries 3
    MaxSessions 2
    PubkeyAuthentication yes
    AuthenticationMethods publickey
    HostbasedAuthentication no
    IgnoreRhosts yes
    PasswordAuthentication no
    PermitEmptyPasswords no
    ChallengeResponseAuthentication no
    UsePAM no
    AllowAgentForwarding no
    AllowTcpForwarding no
    X11Forwarding no
    PermitTTY yes
    PrintMotd no
    ClientAliveInterval 300
    ClientAliveCountMax 2
    TCPKeepAlive no
    AllowUsers docker
    KexAlgorithms curve25519-sha256@libssh.org,ecdh-sha2-nistp521,ecdh-sha2-nistp384,ecdh-sha2-nistp256
    Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr
    MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com
    EOF

    systemctl restart sshd

    # Kamal-specific sudo permissions
    print_message "${YELLOW}" "Configuring sudo for Kamal..."
    cat <<EOF > /etc/sudoers.d/docker-user
    docker ALL=(ALL) NOPASSWD: /usr/sbin/service docker restart
    docker ALL=(ALL) NOPASSWD: /bin/systemctl restart docker
    EOF
    chmod 440 /etc/sudoers.d/docker-user

    # System hardening
    print_message "${YELLOW}" "Hardening system..."
    cat <<EOF >> /etc/sysctl.conf
    # Network security
    net.ipv4.conf.all.send_redirects = 0
    net.ipv4.conf.default.send_redirects = 0
    net.ipv4.conf.all.accept_redirects = 0
    net.ipv4.conf.default.accept_redirects = 0
    net.ipv4.conf.all.rp_filter = 1
    net.ipv4.conf.default.rp_filter = 1
    net.ipv4.icmp_echo_ignore_broadcasts = 1
    net.ipv4.icmp_ignore_bogus_error_responses = 1
    net.ipv4.conf.all.accept_source_route = 0
    net.ipv4.conf.default.accept_source_route = 0
    net.ipv6.conf.all.accept_source_route = 0
    net.ipv6.conf.default.accept_source_route = 0
    net.ipv4.tcp_syncookies = 1
    # Docker needs IPv4 forwarding
    net.ipv4.ip_forward = 1
    # System limits for Kamal/Docker
    fs.file-max = 65535
    kernel.pid_max = 65536
    net.ipv4.ip_local_port_range = 1024 65000
    net.ipv4.tcp_tw_reuse = 1
    vm.max_map_count = 262144
    EOF

    sysctl -p

    # Log rotation for Docker
    cat <<EOF > /etc/logrotate.d/docker-logs
    /var/lib/docker/containers/*/*.log {
    rotate 7
    daily
    compress
    missingok
    delaycompress
    copytruncate
    size 100M
    }
    EOF

    # Enable security services
    systemctl enable sysstat auditd
    systemctl start sysstat auditd

    # Configure unattended upgrades
    cat <<EOF > /etc/apt/apt.conf.d/50unattended-upgrades
    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::Remove-Unused-Dependencies "true";
    Unattended-Upgrade::Remove-New-Unused-Dependencies "true";
    Unattended-Upgrade::AutoFixInterruptedDpkg "true";
    Unattended-Upgrade::MinimalSteps "true";
    EOF

    # Clean up
    apt-get autoremove -y
    apt-get clean

    print_message "${GREEN}" "Setup complete! Please reboot the system."
    print_message "${YELLOW}" "Important next steps:"
    print_message "${YELLOW}" "1. Configure your load balancer to point to this host"
    print_message "${YELLOW}" "2. Set up your .kamal/secrets file on your deployment machine"
    print_message "${YELLOW}" "3. Run 'kamal deploy' to deploy your application"