A subnet scan will flag weak SSH MAC algorithms on most of your fleet. Nobody configured that. It is the OS default list.
Here is how to fix it across containers, VMs and vendor NAS units without doing every host by hand.
Restrict the MACs directive in sshd_config to SHA-2 algorithms only.
MACs hmac-sha2-256,hmac-sha2-512,[email protected],[email protected]
Across a fleet, spend the extra few lines. Back up before editing. Validate before restarting. Restore the backup if validation fails.
set -uo pipefail
CONF=/etc/ssh/sshd_config
BAK="$CONF.bak.$(date +%s)"
cp "$CONF" "$BAK"
STRONG='MACs hmac-sha2-256,hmac-sha2-512,[email protected],[email protected]'
if grep -q '^MACs ' "$CONF"; then
sed -i "s/^MACs .*/$STRONG/" "$CONF"
else
echo "$STRONG" >> "$CONF"
fi
if sshd -t 2>/tmp/sshd_t_err; then
systemctl restart ssh 2>/dev/null || systemctl restart sshd 2>/dev/null
echo HARDEN_OK
else
cp "$BAK" "$CONF"
echo "HARDEN_FAIL_SYNTAX: $(cat /tmp/sshd_t_err)"
fi
Most of your containers probably never got a dedicated SSH keypair. Only the ones that needed direct SSH for something else.
Do not generate and authorize a new keypair on every one of them for a single fleet change.
Use your hypervisor instead if it can run commands inside a container without SSH. On Proxmox that is
pct exec. This is the better default for any one-off fleet change, not just this one.
Match block breaks a naive append. Some vendor NAS
sshd_config files end with Match User blocks. Append a global directive at
end-of-file and it lands inside the last Match block. OpenSSH rejects that outright.
Global directives have to come before any Match block. Insert before the first
Match line instead.
awk -v line="$STRONG" '
!inserted && /^Match/ { print line; inserted=1 }
{ print }
END { if (!inserted) print line }
' "$CONF" > "$CONF.new" && mv "$CONF.new" "$CONF"
scp silently fails.
Modern OpenSSH scp defaults to SFTP. Force the legacy protocol with command
scp -O.
MACs
directive is only consulted when a non-AEAD cipher is selected. Force one to test for real.
ssh -o Ciphers=aes256-ctr -o MACs=hmac-md5 <host>
# should be correctly rejected:
# "Unable to negotiate with <host> port 22: no matching MAC found"
Occasionally a VM has neither a dedicated SSH key nor a working guest-agent shortcut.
Check whether the default user really is root before calling it unreachable. Password auth may
be on for a non-root account nobody thought to try.
Then generate a dedicated keypair, install it once by hand, and treat that host like every other one from then on.
Hosts that connect as a non-root user need an interactive sudo password. No automated script
can supply that.
Add a standing passwordless-sudo rule once per host instead of doing those by hand every time.
echo '<user> ALL=(ALL) NOPASSWD: ALL' | sudo tee /etc/sudoers.d/090-nopasswd
sudo chmod 0440 /etc/sudoers.d/090-nopasswd
sudo visudo -c
This is a real tradeoff. It is standing root-equivalent access. Weigh it against how often you actually run automated fleet-wide changes.
sshd fleet-wide. Open a fresh connection
to every host and confirm it did not lock itself out. Keep the backup config file. It is your rollback, not
cleanup debris.