#!/usr/bin/env bash # ============================================================================= # Umbermark read-only scan collector -- REVIEW THIS SCRIPT BEFORE RUNNING. # # It runs ONLY the read-only diagnostic commands listed below (each passed to # `_run`) and writes a JSON bundle of their output to stdout. It changes NOTHING # on this host: no installs, no config edits (the only write is a temp file # holding your bundle, and only if an upload fails). Upload the bundle to # Umbermark to get your hardening score -- we never touch your server. # # Run with sudo (or as root) for full coverage of privileged checks: # sudo bash umbermark-collect.sh # ============================================================================= set -u TARGET="${1:-uploaded}" _jstr() { printf '"%s"' "$(printf '%s' "$1" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g')"; } _run() { # _run cmd="$1"; need="$2" if [ "$need" = "1" ] && [ "$(id -u)" != "0" ]; then out="$(sudo -n sh -c "$cmd" 2>/dev/null)"; rc=$? else out="$(sh -c "$cmd" 2>/dev/null)"; rc=$? fi printf '%s%s:{"exit_code":%s,"stdout_b64":"%s"}' \ "$SEP" "$(_jstr "$cmd")" "$rc" "$(printf '%s' "$out" | base64 | tr -d '\n')" SEP=',' } echo "[umbermark] Running 26 read-only checks (56 commands)..." >&2 BUNDLE="$( printf '{"schema_version":1,"target":%s,"commands":{' "$(_jstr "$TARGET")" SEP='' _run 'cat /etc/os-release 2>/dev/null' 0 _run 'uname -m 2>/dev/null' 0 _run 'ip -o addr show 2>/dev/null' 0 _run 'nft list ruleset 2>/dev/null' 1 _run 'iptables -S 2>/dev/null' 1 _run 'ip6tables -S 2>/dev/null' 1 _run 'ufw status 2>/dev/null' 1 _run 'ufw status verbose 2>/dev/null' 1 _run 'ss -tln 2>/dev/null' 1 _run 'iptables -t nat -S 2>/dev/null' 1 _run 'ss -tlnp 2>/dev/null' 1 _run 'grep -HE '\''^[[:space:]]*-?(auth|@include)'\'' /etc/pam.d/sshd /etc/pam.d/sudo /etc/pam.d/common-auth /etc/pam.d/system-auth 2>/dev/null' 1 _run 'sshd -T 2>/dev/null' 1 _run 'grep -rEin '\''^[[:space:]]*(Match[[:space:]]|PasswordAuthentication)'\'' /etc/ssh/sshd_config /etc/ssh/sshd_config.d/ 2>/dev/null' 1 _run 'awk -F: '\''NR==FNR{s[$1]=$7;next}{print $1, "[" substr($2,1,1) "]", s[$1]}'\'' /etc/passwd /etc/shadow 2>/dev/null' 1 _run 'grep -i '\''^[[:space:]]*PermitRootLogin'\'' /etc/ssh/sshd_config 2>/dev/null' 0 _run 'grep -rEh '\''^[[:space:]]*[^#].*NOPASSWD'\'' /etc/sudoers /etc/sudoers.d/ 2>/dev/null' 1 _run 'for u in $(awk -F: '\''$7 ~ /[/](bash|zsh|sh|dash|ksh|csh|tcsh|fish)$/ {print $1}'\'' /etc/passwd); do echo "USER: $u"; chage -l "$u" 2>&1; done' 1 _run 'journalctl _COMM=sshd --since '\''7 days ago'\'' --no-pager 2>/dev/null | grep -cE '\''Failed password|Invalid user'\''' 1 _run 'systemctl is-active fail2ban 2>/dev/null' 0 _run 'fail2ban-client status 2>/dev/null' 1 _run 'cat /proc/version 2>/dev/null' 0 _run 'dnf -q check-update 2>/dev/null' 0 _run '/usr/lib/update-notifier/apt-check 2>&1' 0 _run 'apt-get -s -o Debug::NoLocking=true upgrade 2>/dev/null' 0 _run 'apt list --upgradable 2>/dev/null' 0 _run 'dnf -q updateinfo list security 2>/dev/null' 0 _run '[ -f /var/run/reboot-required ] && echo yes || echo no' 0 _run 'dnf needs-restarting -r 2>/dev/null; echo exit:$?' 1 _run 'systemctl is-enabled apt-daily-upgrade.timer 2>/dev/null' 0 _run 'systemctl is-enabled apt-daily.timer 2>/dev/null' 0 _run 'apt-config dump APT::Periodic 2>/dev/null' 0 _run 'dpkg-query -W -f='\''${db:Status-Abbrev}'\'' unattended-upgrades 2>/dev/null' 0 _run 'systemctl is-enabled unattended-upgrades.timer 2>/dev/null' 0 _run 'systemctl is-enabled dnf-automatic.timer 2>/dev/null' 0 _run 'systemctl is-enabled dnf5-automatic.timer 2>/dev/null' 0 _run 'cat /etc/dnf/automatic.conf 2>/dev/null' 0 _run 'cat /usr/share/dnf5/dnf5-plugins/automatic.conf 2>/dev/null' 0 _run 'ls /etc/apt/trusted.gpg.d/ 2>/dev/null | wc -l' 0 _run 'rpm -qa gpg-pubkey 2>/dev/null | wc -l' 0 _run 'firewall-cmd --state 2>/dev/null' 1 _run 'firewall-cmd --get-default-zone 2>/dev/null' 1 _run 'firewall-cmd --list-all-zones 2>/dev/null' 1 _run 'timedatectl status 2>/dev/null' 0 _run 'cat /etc/resolv.conf 2>/dev/null' 0 _run 'getent hosts deb.debian.org 2>/dev/null' 0 _run 'df -P / 2>/dev/null | awk '\''NR==2 {print $5}'\''' 0 _run 'findmnt /tmp -no OPTIONS 2>/dev/null' 0 _run 'systemctl --failed --no-legend 2>/dev/null' 0 _run 'vmstat 1 3 2>/dev/null | tail -1' 0 _run '[ -d /var/log/journal ] && echo yes || echo no' 0 _run 'grep -E '\''^[[:space:]]*Storage'\'' /etc/systemd/journald.conf 2>/dev/null' 0 _run 'aa-status 2>/dev/null' 1 _run 'getenforce 2>/dev/null' 0 _run 'sh -c '\''{ timeout 20 find / -xdev -type f -perm -2 ! -path "/tmp/*" ! -path "/var/tmp/*" ! -path "/dev/shm/*" ! -path "/proc/*" ! -path "/sys/*" 2>/dev/null; [ $? -eq 124 ] && echo __UMBERMARK_FIND_TIMEOUT__; } | head -100'\''' 1 _run 'sh -c '\''{ timeout 20 find / -xdev -type f -perm -4000 ! -path "/proc/*" ! -path "/sys/*" 2>/dev/null; [ $? -eq 124 ] && echo __UMBERMARK_FIND_TIMEOUT__; } | head -200'\''' 1 printf '}}' )" RESP="$(printf '%s' "$BUNDLE" | curl -sS -X POST \ -H "Content-Type: application/json" -H "Accept: text/plain" \ --data-binary @- -w '\n%{http_code}' 'https://umbermark.starhold.app/scan/upload' 2>&1)" CURL_RC=$? CODE="${RESP##*$'\n'}" BODY="${RESP%$'\n'*}" case "$CODE" in 2??) OK=1 ;; *) OK=0 ;; esac if [ "$CURL_RC" -eq 0 ] && [ "$OK" = "1" ]; then printf '%s\n' "$BODY" elif [ "$CURL_RC" -eq 0 ] && [ "$CODE" = "429" ]; then echo "[umbermark] $BODY" >&2 exit 1 else [ "$CURL_RC" -ne 0 ] && CODE="000 (network error: $BODY)" echo "[umbermark] ERROR: upload failed, HTTP status $CODE" >&2 FALLBACK="$(mktemp 2>/dev/null || echo /tmp/umbermark-bundle.json)" printf '%s\n' "$BUNDLE" > "$FALLBACK" 2>/dev/null echo "[umbermark] Your bundle was saved to $FALLBACK" >&2 echo "[umbermark] Paste its contents at the upload page to get your report manually." >&2 exit 1 fi