Skip to content

Security Hardening

Advanced security hardening for Mailborder deployments.

System Hardening

Operating System Security

Update system packages:

sudo apt update
sudo apt upgrade
sudo apt dist-upgrade

Enable automatic security updates:

sudo apt install unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades

Configure automatic updates:

sudo nano /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::AutoFixInterruptedDpkg "true";
Unattended-Upgrade::MinimalSteps "true";
Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";
Unattended-Upgrade::Remove-Unused-Dependencies "true";
Unattended-Upgrade::Automatic-Reboot "false";

Disable unnecessary services:

# List enabled services
systemctl list-unit-files --state=enabled

# Disable unnecessary ones
sudo systemctl disable bluetooth
sudo systemctl disable cups
sudo systemctl disable avahi-daemon

Remove unnecessary packages:

sudo apt autoremove
sudo apt purge telnet rsh-client

Firewall Configuration

Install and configure UFW:

sudo apt install ufw

# Default policies
sudo ufw default deny incoming
sudo ufw default allow outgoing

# Allow SSH (configure before enabling!)
sudo ufw allow 22/tcp

# Allow email services
sudo ufw allow 25/tcp   # SMTP
sudo ufw allow 465/tcp  # SMTPS
sudo ufw allow 587/tcp  # Submission

# Allow web interface
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

# Rate limit SSH
sudo ufw limit 22/tcp

# Enable firewall
sudo ufw enable

Advanced UFW rules:

# Allow from specific IP
sudo ufw allow from 192.168.1.0/24 to any port 22

# Block specific country (using ipset)
sudo apt install ipset xtables-addons-common
sudo ipset create blocklist hash:net
sudo ipset add blocklist 1.2.3.0/24

# Add to UFW
sudo iptables -I INPUT -m set --match-set blocklist src -j DROP

SSH Hardening

Configure SSH securely:

sudo nano /etc/ssh/sshd_config

# Disable root login
PermitRootLogin no

# Use key-based authentication only
PasswordAuthentication no
PubkeyAuthentication yes
ChallengeResponseAuthentication no

# Disable empty passwords
PermitEmptyPasswords no

# Limit login attempts
MaxAuthTries 3
MaxSessions 5

# Disable X11 forwarding
X11Forwarding no

# Use strong ciphers
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com
KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org

# Idle timeout
ClientAliveInterval 300
ClientAliveCountMax 2

# Banner
Banner /etc/issue.net

Restart SSH:

sudo systemctl restart sshd

Setup SSH key authentication:

# Generate key (on client)
ssh-keygen -t ed25519 -C "admin@mailborder"

# Copy to server
ssh-copy-id user@mailborder-server

# Test before disabling password auth!
ssh user@mailborder-server

Fail2ban Configuration

Install Fail2ban:

sudo apt install fail2ban

Configure Fail2ban:

sudo tee /etc/fail2ban/jail.local << 'EOF'
[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 5
destemail = admin@example.com
sendername = Fail2Ban
action = %(action_mwl)s

[sshd]
enabled = true
port = 22
logpath = /var/log/auth.log

[nginx-http-auth]
enabled = true
port = 80,443
logpath = /var/log/nginx/error.log

[nginx-limit-req]
enabled = true
port = 80,443
logpath = /var/log/nginx/error.log

[postfix-sasl]
enabled = true
port = smtp,465,submission
logpath = /var/log/mail.log
maxretry = 3

[mailborder-auth]
enabled = true
port = 80,443
logpath = /var/log/mailborder/auth.log
maxretry = 5
findtime = 600
bantime = 7200
EOF

Create custom filter:

sudo tee /etc/fail2ban/filter.d/mailborder-auth.conf << 'EOF'
[Definition]
failregex = ^.*Failed login attempt from <HOST>.*$
            ^.*Authentication failed for user .* from <HOST>.*$
ignoreregex =
EOF

Restart Fail2ban:

sudo systemctl restart fail2ban

Check status:

sudo fail2ban-client status
sudo fail2ban-client status sshd
sudo fail2ban-client status mailborder-auth

Application Hardening

Mailborder Configuration

Security settings:

sudo nano /etc/mailborder/mailborder.conf

[security]
# Enforce HTTPS
force_https = true
https_only = true

# Security headers
hsts_enabled = true
hsts_max_age = 31536000
frame_options = DENY
content_type_nosniff = true
xss_protection = true

# Session security
session_secure = true
session_httponly = true
session_samesite = Strict
session_lifetime = 3600
session_regenerate = true

# Rate limiting
rate_limit_enabled = true
rate_limit_requests = 100
rate_limit_window = 60

# CSRF protection
csrf_protection = true
csrf_token_lifetime = 3600

# Password policy
password_min_length = 12
password_require_uppercase = true
password_require_lowercase = true
password_require_numbers = true
password_require_special = true
password_history = 5
password_expiry_days = 90

# Account lockout
lockout_enabled = true
lockout_attempts = 5
lockout_duration = 900
lockout_window = 300

# 2FA enforcement
require_2fa = true
require_2fa_for_admin = true

# Audit logging
audit_enabled = true
audit_log_file = /var/log/mailborder/audit.log

PHP Hardening

Secure PHP configuration:

sudo nano /etc/php/8.2/fpm/php.ini

; Disable dangerous functions
disable_functions = exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source

; Hide PHP version
expose_php = Off

; Error handling
display_errors = Off
display_startup_errors = Off
log_errors = On
error_log = /var/log/php/error.log

; File uploads
file_uploads = On
upload_max_filesize = 50M
max_file_uploads = 10

; Resource limits
max_execution_time = 60
max_input_time = 60
memory_limit = 256M
post_max_size = 50M

; Session security
session.cookie_httponly = 1
session.cookie_secure = 1
session.cookie_samesite = Strict
session.use_strict_mode = 1
session.use_only_cookies = 1

; Disable dangerous wrappers
allow_url_fopen = Off
allow_url_include = Off

Restart PHP-FPM:

sudo systemctl restart php8.2-fpm

Nginx Security Headers

Add security headers:

sudo nano /etc/nginx/sites-available/mailborder

server {
    listen 443 ssl http2;
    server_name mailborder.example.com;

    # SSL configuration
    ssl_certificate /etc/ssl/certs/mailborder.crt;
    ssl_certificate_key /etc/ssl/private/mailborder.key;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5:!3DES;
    ssl_prefer_server_ciphers on;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;
    ssl_stapling on;
    ssl_stapling_verify on;

    # Security headers
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
    add_header X-Frame-Options "DENY" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';" always;
    add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;

    # Hide server version
    server_tokens off;
    more_clear_headers Server;

    # Rate limiting
    limit_req zone=api burst=20 nodelay;
    limit_req_status 429;

    # Client body size
    client_max_body_size 50M;
    client_body_timeout 30s;
    client_header_timeout 30s;

    # Disable unwanted HTTP methods
    if ($request_method !~ ^(GET|POST|PUT|DELETE|HEAD|OPTIONS)$) {
        return 405;
    }

    # Block common exploit attempts
    location ~ /\. {
        deny all;
        access_log off;
        log_not_found off;
    }

    location ~* \.(git|svn|htaccess|htpasswd)$ {
        deny all;
    }

    # Main application
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    # PHP processing
    location ~ \.php$ {
        fastcgi_pass unix:/run/php/php8.2-fpm.sock;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

        # Security parameters
        fastcgi_hide_header X-Powered-By;
        fastcgi_intercept_errors on;
        fastcgi_buffer_size 16k;
        fastcgi_buffers 4 16k;
    }
}

# Redirect HTTP to HTTPS
server {
    listen 80;
    server_name mailborder.example.com;
    return 301 https://$server_name$request_uri;
}

Reload Nginx:

sudo nginx -t
sudo systemctl reload nginx

Database Security

MariaDB Hardening

Run mysql_secure_installation:

sudo mysql_secure_installation

Secure MariaDB configuration:

sudo nano /etc/mysql/mariadb.conf.d/50-server.cnf

[mysqld]
# Bind to localhost only
bind-address = 127.0.0.1

# Disable LOAD DATA LOCAL
local-infile = 0

# Enable SSL
ssl-ca = /etc/mysql/ssl/ca-cert.pem
ssl-cert = /etc/mysql/ssl/server-cert.pem
ssl-key = /etc/mysql/ssl/server-key.pem

# Logging
log-error = /var/log/mysql/error.log
general_log = 0
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 2

# Secure file operations
secure-file-priv = /var/lib/mysql-files

# User validation
plugin-load-add = simple_password_check.so
simple_password_check_minimal_length = 12

Create restricted database user:

-- Create user with limited privileges
CREATE USER 'mailborder'@'localhost' IDENTIFIED BY 'secure_password';

-- Grant only necessary privileges
GRANT SELECT, INSERT, UPDATE, DELETE ON mailborder.* TO 'mailborder'@'localhost';

-- No GRANT privilege
FLUSH PRIVILEGES;

Encrypt backups:

# Backup with encryption
sudo mysqldump mailborder | gzip | \
  openssl enc -aes-256-cbc -salt -pbkdf2 -out /backups/db.sql.gz.enc

Redis Security

Secure Redis:

sudo nano /etc/redis/redis.conf

# Bind to localhost only
bind 127.0.0.1 ::1

# Require authentication
requirepass your_strong_redis_password

# Rename dangerous commands
rename-command FLUSHDB ""
rename-command FLUSHALL ""
rename-command CONFIG "CONFIG_a1b2c3d4"
rename-command SHUTDOWN ""

# Disable protected mode (when bind is set)
protected-mode yes

# Maximum clients
maxclients 10000

# Snapshotting disabled (if using as cache only)
save ""

# Logging
loglevel notice
logfile /var/log/redis/redis-server.log

Restart Redis:

sudo systemctl restart redis-server

Email Security

Postfix Hardening

Secure Postfix configuration:

sudo nano /etc/postfix/main.cf

# SMTP restrictions
smtpd_helo_required = yes
smtpd_helo_restrictions =
    permit_mynetworks,
    reject_invalid_helo_hostname,
    reject_non_fqdn_helo_hostname,
    reject_unknown_helo_hostname

smtpd_sender_restrictions =
    permit_mynetworks,
    reject_non_fqdn_sender,
    reject_unknown_sender_domain

smtpd_recipient_restrictions =
    permit_mynetworks,
    permit_sasl_authenticated,
    reject_unauth_destination,
    reject_invalid_hostname,
    reject_non_fqdn_recipient,
    reject_unknown_recipient_domain,
    reject_unauth_pipelining,
    reject_rbl_client zen.spamhaus.org,
    reject_rbl_client bl.spamcop.net

# Rate limiting
smtpd_client_connection_count_limit = 10
smtpd_client_connection_rate_limit = 30
smtpd_client_message_rate_limit = 100
smtpd_client_recipient_rate_limit = 100

# Size limits
message_size_limit = 52428800
mailbox_size_limit = 0

# Disable VRFY and EXPN
disable_vrfy_command = yes

# TLS security
smtpd_tls_security_level = may
smtpd_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
smtpd_tls_ciphers = high
smtpd_tls_exclude_ciphers = aNULL, eNULL, EXPORT, DES, RC4, MD5, PSK, aECDH, EDH-DSS-DES-CBC3-SHA, EDH-RSA-DES-CBC3-SHA, KRB5-DES, CBC3-SHA
smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
smtpd_tls_mandatory_ciphers = high

smtp_tls_security_level = may
smtp_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
smtp_tls_ciphers = high

# SASL authentication
smtpd_sasl_auth_enable = yes
smtpd_sasl_security_options = noanonymous, noplaintext
smtpd_sasl_tls_security_options = noanonymous

Reload Postfix:

sudo postfix reload

SPF, DKIM, DMARC

Ensure authentication is enforced:

# Check SPF records
dig +short TXT example.com | grep spf

# Check DKIM signing
echo "Test" | mail -s "DKIM Test" test@gmail.com
# Check headers in received email

# Check DMARC policy
dig +short TXT _dmarc.example.com

Strict DMARC policy:

v=DMARC1; p=reject; pct=100; rua=mailto:dmarc@example.com; ruf=mailto:dmarc@example.com; fo=1; adkim=s; aspf=s

Monitoring and Auditing

Security Monitoring

Install monitoring tools:

sudo apt install auditd aide lynis

Configure auditd:

sudo nano /etc/audit/rules.d/mailborder.rules

# Monitor configuration changes
-w /etc/mailborder/ -p wa -k mailborder_config
-w /etc/postfix/ -p wa -k postfix_config
-w /etc/nginx/ -p wa -k nginx_config

# Monitor authentication
-w /var/log/auth.log -p wa -k auth_log
-w /var/log/mailborder/auth.log -p wa -k mailborder_auth

# Monitor user changes
-w /etc/passwd -p wa -k passwd_changes
-w /etc/group -p wa -k group_changes
-w /etc/shadow -p wa -k shadow_changes

# Monitor sudoers
-w /etc/sudoers -p wa -k sudoers_changes

Restart auditd:

sudo systemctl restart auditd

Initialize AIDE:

sudo aideinit
sudo cp /var/lib/aide/aide.db.new /var/lib/aide/aide.db

Run security scan:

sudo lynis audit system

Log Monitoring

Configure centralized logging:

sudo apt install rsyslog

Forward logs to remote server:

sudo nano /etc/rsyslog.d/50-mailborder.conf

# Forward all logs to remote syslog server
*.* @@syslog-server.example.com:514

Monitor for security events:

sudo tee /usr/local/bin/mb-security-monitor.sh << 'EOF'
#!/bin/bash

# Check for failed logins
FAILED_LOGINS=$(grep "Failed password" /var/log/auth.log | wc -l)
if [ $FAILED_LOGINS -gt 10 ]; then
    echo "ALERT: $FAILED_LOGINS failed login attempts" | \
      mail -s "Security Alert" admin@example.com
fi

# Check for privilege escalation
SUDO_FAILURES=$(grep "sudo.*COMMAND" /var/log/auth.log | grep "NOT" | wc -l)
if [ $SUDO_FAILURES -gt 0 ]; then
    echo "ALERT: $SUDO_FAILURES sudo failures" | \
      mail -s "Security Alert" admin@example.com
fi

# Check for file modifications
if [ -f /tmp/aide-changes.txt ]; then
    mail -s "AIDE File Changes" admin@example.com < /tmp/aide-changes.txt
fi
EOF

sudo chmod +x /usr/local/bin/mb-security-monitor.sh

Schedule monitoring:

echo "0 * * * * /usr/local/bin/mb-security-monitor.sh" | sudo crontab -

Compliance and Best Practices

Security Checklist

System Security:
□ OS packages up to date
□ Automatic security updates enabled
□ Unnecessary services disabled
□ Firewall configured and enabled
□ SSH hardened (key-only auth)
□ Fail2ban configured
□ SELinux/AppArmor enabled

Application Security:
□ HTTPS enforced
□ Security headers configured
□ CSRF protection enabled
□ Rate limiting enabled
□ 2FA required for admin
□ Password policy enforced
□ Session security configured
□ Audit logging enabled

Database Security:
□ Database users restricted
□ SSL/TLS enabled
□ Bind to localhost
□ Dangerous commands disabled
□ Backups encrypted

Email Security:
□ SPF configured
□ DKIM signing enabled
□ DMARC policy enforced
□ TLS encryption required
□ Authentication required
□ Rate limiting configured
□ RBL checking enabled

Monitoring:
□ System monitoring enabled
□ Log aggregation configured
□ Security alerts configured
□ File integrity monitoring
□ Regular security audits

Regular Security Tasks

Daily: - Review authentication logs - Check failed login attempts - Monitor system alerts

Weekly: - Review firewall logs - Check for security updates - Analyze access patterns

Monthly: - Run security audit (Lynis) - Review user accounts - Check file integrity (AIDE) - Review SSL certificates - Test backup restoration

Quarterly: - Penetration testing - Security policy review - Compliance audit - Disaster recovery test

See Also