|
|
@@ -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 |