Menu
AkurAI-Build
publicLatest change e9497c4d8313c394e907ae962fdfe3bb5025013e - Add mail-server backup, audit, and update operations by Ólafur Búi Ólafsson
set -uo pipefail
host="$1"
warn_days="$2"
[[ "$host" =~ ^[A-Za-z0-9.-]+$ ]]
[[ "$warn_days" =~ ^[0-9]+$ ]]
failures=0
checks=0
report() { printf '%s %s %s\n' "$1" "$2" "$3"; }
check() {
# check <name> <detail> <command...>
local name="$1" detail="$2"
shift 2
checks=$((checks + 1))
if "$@" >/dev/null 2>&1; then
report PASS "$name" "$detail"
else
failures=$((failures + 1))
report FAIL "$name" "$detail"
fi
}
loopback_only() {
local token
for token in $1; do
case "$token" in
127.* | '[::1]'* | localhost) ;;
*) return 1 ;;
esac
done
}
for unit in postfix@- dovecot opendkim opendmarc spamass-milter fail2ban; do
check "unit:$unit" active systemctl is-active --quiet "$unit"
done
ufw_state="$(sudo ufw status 2>/dev/null | head -1 | awk '{print $2}')"
check firewall "ufw=$ufw_state" test "$ufw_state" = active
jails="$(sudo fail2ban-client status 2>/dev/null | sed -n 's/.*Jail list:[[:space:]]*//p' | tr -d '[:space:]')"
for jail in sshd postfix postfix-sasl dovecot; do
check "fail2ban:$jail" "jails=$jails" grep -qx "$jail" <<<"${jails//,/$'\n'}"
done
sshd_password="$(sudo sshd -T 2>/dev/null | awk '$1=="passwordauthentication"{print $2}')"
check ssh_password_auth "passwordauthentication=$sshd_password" test "$sshd_password" = no
sshd_root="$(sudo sshd -T 2>/dev/null | awk '$1=="permitrootlogin"{print $2}')"
check ssh_root_login "permitrootlogin=$sshd_root" test "$sshd_root" != yes
relay="$(postconf -h smtpd_relay_restrictions 2>/dev/null)"
check smtp_relay_closed "smtpd_relay_restrictions=${relay// /,}" \
grep -Eq '(reject|defer)_unauth_destination' <<<"$relay"
networks="$(postconf -h mynetworks 2>/dev/null)"
check smtp_mynetworks "mynetworks=${networks// /,}" loopback_only "$networks"
tls_level="$(postconf -h smtpd_tls_security_level 2>/dev/null)"
check smtp_tls_offered "smtpd_tls_security_level=$tls_level" \
grep -Eqx '(may|encrypt)' <<<"$tls_level"
imap_ssl="$(doveconf -h ssl 2>/dev/null)"
check imap_tls_required "dovecot_ssl=$imap_ssl" test "$imap_ssl" = required
cert="/etc/letsencrypt/live/$host/cert.pem"
cert_days=-1
if sudo test -r "$cert"; then
expiry="$(sudo openssl x509 -enddate -noout -in "$cert" | cut -d= -f2)"
cert_days=$((($(date -u -d "$expiry" +%s) - $(date -u +%s)) / 86400))
fi
check tls_expiry "host=$host days_left=$cert_days warn_at=$warn_days" \
test "$cert_days" -ge "$warn_days"
# Every domain OpenDKIM signs for must publish SPF, DMARC and its selector key.
domains="$(sudo awk '{print $2}' /etc/opendkim/key.table 2>/dev/null | cut -d: -f1 | sort -u)"
for domain in $domains; do
selector="$(sudo awk -v d="$domain" '$2 ~ "^"d":" {split($2, f, ":"); print f[2]; exit}' \
/etc/opendkim/key.table 2>/dev/null)"
spf="$(dig +short @1.1.1.1 TXT "$domain" 2>/dev/null | grep -c 'v=spf1')"
check "dns_spf:$domain" "records=$spf" test "$spf" -ge 1
dmarc="$(dig +short @1.1.1.1 TXT "_dmarc.$domain" 2>/dev/null | grep -c 'v=DMARC1')"
check "dns_dmarc:$domain" "records=$dmarc" test "$dmarc" -ge 1
dkim="$(dig +short @1.1.1.1 TXT "${selector:-mail}._domainkey.$domain" 2>/dev/null | grep -c 'p=')"
check "dns_dkim:$domain" "selector=${selector:-mail} found=$dkim" test "$dkim" -ge 1
done
backup_latest="$(sudo find /var/backups/akurai-mail -maxdepth 1 -type f -name 'akurai-mail-*.tar.gz.gpg' \
-printf '%T@ %p\n' 2>/dev/null | sort -nr | head -1 | cut -d' ' -f2-)"
backup_age=-1
if [ -n "$backup_latest" ]; then
backup_age=$(($(date -u +%s) - $(sudo stat -c %Y "$backup_latest")))
fi
check backup_fresh "age_secs=$backup_age max=93600" \
bash -c '[ "$1" -ge 0 ] && [ "$1" -lt 93600 ]' _ "$backup_age"
backup_result="$(systemctl show akurai-mail-backup.service -p Result --value 2>/dev/null)"
check backup_result "result=$backup_result" test "$backup_result" = success
upgradable="$(apt list --upgradable 2>/dev/null | grep -c '/')"
security="$(apt list --upgradable 2>/dev/null | grep -c -- '-security')"
lists_age=$((($(date -u +%s) - $(stat -c %Y /var/lib/apt/lists 2>/dev/null || echo 0)) / 3600))
check security_patches "security=$security upgradable=$upgradable lists_age_h=$lists_age" \
test "$security" -eq 0
reboot=no
[ -f /var/run/reboot-required ] && reboot=yes
check reboot_pending "reboot_required=$reboot" test "$reboot" = no
disk="$(df --output=pcent / | tail -1 | tr -dc '0-9')"
check disk_headroom "root_used_pct=$disk max=85" test "$disk" -le 85
dovecot_fail="$(sudo journalctl -u dovecot --since '24 hours ago' --no-pager 2>/dev/null |
grep -ci 'auth failed')"
sasl_fail="$(sudo journalctl -u 'postfix@-' --since '24 hours ago' --no-pager 2>/dev/null |
grep -ci 'SASL .* authentication failed')"
banned="$(sudo fail2ban-client banned 2>/dev/null |
grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' | sort -u | wc -l)"
report INFO auth_pressure \
"dovecot_auth_failed_24h=$dovecot_fail sasl_failed_24h=$sasl_fail banned_addresses=$banned"
queue="$(mailq 2>/dev/null | tail -1)"
report INFO mail_queue "${queue:-empty}"
if [ "$failures" -gt 0 ]; then
printf 'audit=fail checks=%s failures=%s\n' "$checks" "$failures"
exit 1
fi
printf 'audit=ok checks=%s failures=0\n' "$checks"