Skip to content

Instantly share code, notes, and snippets.

@kkarimi
Forked from NatElkins/cloud-init.yaml
Created March 8, 2025 22:38
Show Gist options
  • Save kkarimi/6f38cc362571132149f489ffc7061e99 to your computer and use it in GitHub Desktop.
Save kkarimi/6f38cc362571132149f489ffc7061e99 to your computer and use it in GitHub Desktop.

Revisions

  1. @NatElkins NatElkins created this gist Mar 8, 2025.
    435 changes: 435 additions & 0 deletions cloud-init.yaml
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,435 @@
    #cloud-config

    # Enable automatic package updates and upgrades during cloud-init execution
    package_update: true
    package_upgrade: true

    packages:
    # Security and Hardening
    - ufw
    - fail2ban
    - apparmor
    - apparmor-utils
    - auditd
    - audispd-plugins
    - unattended-upgrades
    - acl
    - aide
    - rkhunter

    # Utilities
    - curl
    - wget
    - gnupg
    - ca-certificates
    - apt-transport-https
    - software-properties-common
    - git
    - chrony
    - vim
    - jq

    # System Monitoring
    - sysstat

    # Email Notification (Postfix / Gmail)
    - postfix
    - mailutils
    - libsasl2-modules

    # Create a non-root user with sudo privileges for secure server access
    users:
    - name: deploy
    groups: sudo
    shell: /bin/bash
    sudo: ["ALL=(ALL) NOPASSWD:ALL"]
    ssh_authorized_keys:
    - ${ssh_public_key}

    # Create configuration files with secure settings
    write_files:
    # Secure SSH configuration to prevent unauthorized access
    - path: /etc/ssh/sshd_config
    content: |
    Include /etc/ssh/sshd_config.d/*.conf
    Port 22
    AddressFamily any
    Protocol 2
    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
    PermitRootLogin no
    StrictModes yes
    MaxAuthTries 5
    MaxSessions 5
    PubkeyAuthentication yes
    HostbasedAuthentication no
    IgnoreRhosts yes
    PasswordAuthentication no
    PermitEmptyPasswords no
    ChallengeResponseAuthentication no
    UsePAM yes
    # Security configuration:
    # - Agent forwarding is disabled to prevent potential agent hijacking attacks
    # - TCP forwarding is enabled to allow SSH tunneling for secure access to services
    # (e.g., Docker API, databases, or internal services)
    # - X11 forwarding is disabled as it's not needed on servers
    AllowAgentForwarding no
    AllowTcpForwarding yes
    X11Forwarding no
    PermitTTY yes
    PrintMotd no
    ClientAliveInterval 300
    ClientAliveCountMax 2
    TCPKeepAlive no
    AllowUsers deploy
    # Kernel security parameters to harden against common attacks
    - path: /etc/sysctl.d/99-security.conf
    content: |
    # Disable IP forwarding and redirects for 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
    # Enable reverse path filtering to prevent IP spoofing
    net.ipv4.conf.all.rp_filter = 1
    net.ipv4.conf.default.rp_filter = 1
    # Ignore broadcast ICMP and bogus error responses
    net.ipv4.icmp_echo_ignore_broadcasts = 1
    net.ipv4.icmp_ignore_bogus_error_responses = 1
    # Disable source routing which can be used in packet spoofing
    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
    # Enable SYN cookies to protect against SYN flood attacks
    net.ipv4.tcp_syncookies = 1
    net.ipv4.tcp_max_syn_backlog = 2048
    net.ipv4.tcp_synack_retries = 2
    net.ipv4.tcp_syn_retries = 5
    # Enable IP forwarding for Docker
    net.ipv4.ip_forward = 1
    # System resource limits and performance tuning
    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 hardening settings
    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
    # Filesystem security
    fs.protected_hardlinks = 1
    fs.protected_symlinks = 1
    fs.suid_dumpable = 0
    # Log suspicious packets
    net.ipv4.conf.all.log_martians = 1
    net.ipv4.conf.default.log_martians = 1
    # IPv6 router advertisement settings
    net.ipv6.conf.all.accept_ra = 2
    net.ipv6.conf.default.accept_ra = 2
    # System resource limits for Docker containers
    - path: /etc/security/limits.d/docker.conf
    content: |
    # Increase process and file limits for Docker containers
    * soft nproc 10000
    * hard nproc 10000
    * soft nofile 1048576
    * hard nofile 1048576
    * soft core 0
    * hard core 0
    * soft stack 8192
    * hard stack 8192
    # Docker daemon configuration with IPv6 ULA (Unique Local Address)
    # We use fd00::/64 (ULA) instead of 2001:db8::/64 (documentation prefix) because:
    # 1. ULA addresses are designed for internal network use, similar to IPv4 private addresses
    # 2. They work reliably for Docker Swarm clustering without exposing containers directly to the internet
    # 3. Unlike the documentation prefix (2001:db8), ULA addresses won't be filtered by network equipment
    # 4. This approach provides IPv6 functionality while maintaining security by default
    - path: /etc/docker/daemon.json
    content: |
    {
    "log-driver": "json-file",
    "log-opts": {
    "max-size": "10m",
    "max-file": "3"
    },
    "icc": true,
    "live-restore": true,
    "userland-proxy": false,
    "no-new-privileges": true,
    "default-ulimits": {
    "nofile": {
    "Name": "nofile",
    "Hard": 64000,
    "Soft": 64000
    }
    },
    "features": {
    "buildkit": true
    },
    "experimental": false,
    "default-runtime": "runc",
    "storage-driver": "overlay2",
    "metrics-addr": "127.0.0.1:9323",
    "ipv6": true,
    "fixed-cidr-v6": "fd00::/64",
    "ip6tables": true,
    "builder": {
    "gc": {
    "enabled": true,
    "defaultKeepStorage": "20GB"
    }
    }
    }
    # Custom fail2ban filter for Docker authentication failures
    - path: /etc/fail2ban/filter.d/docker.conf
    content: |
    [Definition]
    # More comprehensive patterns for Docker Swarm authentication failures
    failregex = ^.*level=warning msg=".*: Password authentication failed for user.*" remote="<HOST>:.*"$
    ^.*level=error msg=".*: authentication failed.*" remote="<HOST>:.*"$
    ^.*level=warning msg=".*: failed authentication.*" remote="<HOST>:.*"$
    ignoreregex =
    # Configure fail2ban jails for SSH and Docker
    - path: /etc/fail2ban/jail.local
    content: |
    # Global fail2ban settings
    [DEFAULT]
    bantime = 3600
    findtime = 600
    maxretry = 5
    banaction = ufw
    banaction_allports = ufw
    # SSH protection
    [sshd]
    enabled = true
    port = ssh
    filter = sshd
    logpath = /var/log/auth.log
    maxretry = 10
    bantime = 3600
    # Docker authentication protection
    [docker]
    enabled = true
    filter = docker
    # Docker uses json-file logging driver by default, not journald
    # This configuration targets the default Docker log files directly
    backend = auto
    logpath = /var/lib/docker/containers/*/*.log
    maxretry = 5
    bantime = 3600
    # Log rotation for Docker container logs to prevent disk space issues
    - path: /etc/logrotate.d/docker-logs
    content: |
    /var/lib/docker/containers/*/*.log {
    rotate 7
    daily
    compress
    size=100M
    missingok
    delaycompress
    copytruncate
    }
    # Weekly Docker cleanup script to manage disk space
    - path: /etc/cron.weekly/docker-cleanup
    permissions: "0755"
    content: |
    #!/bin/bash
    # Weekly cleanup of Docker resources
    # Only removes unused resources (dangling images, stopped containers, unused networks and volumes)
    # Keeps images used within the last 30 days
    docker system prune -f --filter "until=720h"
    # Keep at least 20GB of builder cache for faster builds
    docker builder prune -f --keep-storage=20GB
    # Helper script for Docker Swarm initialization
    - path: /usr/local/bin/initialize-swarm.sh
    permissions: "0755"
    content: |
    #!/bin/bash
    # Helper script to initialize Docker Swarm with IPv4 for management and IPv6 enabled
    # Get the primary IPv4 address
    IPV4_ADDR=$(hostname -I | awk '{print $1}')
    # Initialize the swarm with IPv4 for management traffic
    echo "Initializing Docker Swarm with IPv4 address: $IPV4_ADDR"
    docker swarm init --advertise-addr $IPV4_ADDR
    echo ""
    echo "Swarm initialized successfully!"
    echo ""
    echo "To create an IPv6-enabled overlay network, run:"
    echo "docker network create --driver overlay --ipv6 --subnet=fd00::/80 --subnet=172.20.0.0/16 my-overlay-network"
    echo ""
    echo "Worker join token:"
    docker swarm join-token worker | grep docker
    echo ""
    echo "Manager join token:"
    docker swarm join-token manager | grep docker
    # Audit rules to monitor Docker-related files and commands
    - path: /etc/audit/rules.d/audit.rules
    content: |
    # Monitor Docker binaries, configuration files, and service files for changes
    -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
    -w /var/lib/docker/swarm -k docker-swarm
    # Configure automatic security updates
    - path: /etc/apt/apt.conf.d/50unattended-upgrades
    content: |
    # Define which updates to automatically install (security updates only)
    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 "true";
    Unattended-Upgrade::Automatic-Reboot-Time "02:00";
    # Postfix configuration for outbound email alerts
    - path: /etc/postfix/main.cf
    content: |
    # Basic Postfix settings
    smtpd_banner = $myhostname ESMTP $mail_name
    biff = no
    append_dot_mydomain = no
    readme_directory = no
    # TLS settings for secure mail transmission
    smtp_tls_security_level = encrypt
    smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt
    smtp_use_tls = yes
    # Placeholder value, replaced in rumcmd section
    myhostname = localhost
    # Mail routing configuration
    alias_maps = hash:/etc/aliases
    alias_database = hash:/etc/aliases
    myorigin = /etc/mailname
    mydestination = $myhostname, localhost.$mydomain, localhost
    relayhost = [smtp.gmail.com]:587
    mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
    mailbox_size_limit = 0
    recipient_delimiter = +
    inet_interfaces = loopback-only
    inet_protocols = all
    # SASL authentication for Gmail relay
    smtp_sasl_auth_enable = yes
    smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
    smtp_sasl_security_options = noanonymous
    smtp_sasl_tls_security_options = noanonymous
    smtp_sasl_mechanism_filter = plain
    # Gmail SMTP credentials for outbound email
    - path: /etc/postfix/sasl_passwd
    permissions: "0600"
    content: |
    # Gmail SMTP authentication credentials (replace with your actual credentials)
    [smtp.gmail.com]:587 [email protected]:your-app-password
    # Commands to run after file creation to configure the system
    runcmd:
    # Apply kernel parameters
    - sysctl --system
    # Disable default time synchronization in favor of chrony
    - systemctl stop systemd-timesyncd || true
    - systemctl disable systemd-timesyncd || true

    # Configure UFW firewall with secure defaults
    - ufw --force reset
    - ufw default deny incoming
    - ufw default allow outgoing
    - ufw allow ssh
    - ufw allow http
    - ufw allow https
    # Docker Swarm ports
    - ufw allow 2376/tcp # Docker TLS port
    - ufw allow 2377/tcp # Docker Swarm cluster management
    - ufw allow 7946/tcp # Container network discovery
    - ufw allow 7946/udp # Container network discovery
    - ufw allow 4789/udp # Container overlay network
    - ufw --force enable

    # Install Docker from official repo
    - apt-get remove -y docker docker-engine docker.io containerd runc || true
    - curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor | tee /usr/share/keyrings/docker-archive-keyring.gpg > /dev/null
    - echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) 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

    # Enable and start security services
    - systemctl enable apparmor
    - systemctl start apparmor
    - aide --config=/etc/aide/aide.conf --init
    - mv /var/lib/aide/aide.db.new /var/lib/aide/aide.db
    - systemctl enable docker fail2ban auditd chrony
    - systemctl restart docker fail2ban auditd chrony
    - systemctl reload ssh
    - apt-get autoremove -y
    - apt-get clean

    # Configure Postfix for outbound email
    - postmap /etc/postfix/sasl_passwd
    - chmod 600 /etc/postfix/sasl_passwd*
    # Try to use FQDN for Postfix hostname, which is better for mail delivery
    # hostname -f attempts to get the fully-qualified domain name
    # If that fails (returns non-zero exit code), fall back to the short hostname
    # This ensures proper mail headers regardless of cloud provider hostname configuration
    - |
    if hostname -f >/dev/null 2>&1; then
    sed -i "s/myhostname = .*/myhostname = $(hostname -f)/" /etc/postfix/main.cf
    else
    sed -i "s/myhostname = .*/myhostname = $(hostname)/" /etc/postfix/main.cf
    fi
    - systemctl restart postfix
    - echo "Server security setup complete for $(hostname)" | mail -s "Server Setup: $(hostname)" [email protected]

    # Remove any scheduled report jobs in favor of event-based alerting
    - rm -f /etc/cron.d/security-report || true

    # Enable system activity reporting
    - sed -i 's/ENABLED=no/ENABLED=yes/' /etc/default/sysstat
    - systemctl restart sysstat