← All four scripts

harden-3-ssh.sh

sshd hardening. Can lock you out, so it refuses to run without a working key and arms an automatic rollback.

Read this, do not pipe it. These scripts run as root. Copy the source into a file on your own machine, read it, and run it yourself. Nobody should curl a root shell script straight off a website, including this one.

#!/usr/bin/env bash # harden-3-ssh.sh — sshd hardening. # # ⚠️ THIS CAN LOCK YOU OUT. Protections, in order: # 1. Refuses to run if $ADMIN_USER has no authorized_keys (the classic way # people lock themselves out: disable passwords with no key installed). # 2. `sshd -t` validates the config BEFORE anything is reloaded. # 3. A rollback timer restores the previous sshd_config and reloads. # 4. You confirm from a SECOND session before the rollback is cancelled. # # sudo DRY_RUN=1 ./harden-3-ssh.sh # sudo ./harden-3-ssh.sh cd "$(dirname "${BASH_SOURCE[0]}")" || exit 1 # shellcheck source=lib.sh source ./lib.sh need_root # Defaults to the human who invoked sudo, which is right on almost every box. ADMIN_USER="${ADMIN_USER:-${SUDO_USER:-$(id -un)}}" ROLLBACK_MIN="${ROLLBACK_MIN:-10}" SSHD_CONF=/etc/ssh/sshd_config DROPIN=/etc/ssh/sshd_config.d/99-ssh-hardening.conf ROLLBACK_BIN=/usr/local/sbin/ssh-harden-rollback head1 "Pre-flight (refusing is safer than locking you out)" id "$ADMIN_USER" >/dev/null 2>&1 || { err "user '$ADMIN_USER' does not exist"; exit 1; } AK="$(getent passwd "$ADMIN_USER" | cut -d: -f6)/.ssh/authorized_keys" if [ ! -s "$AK" ]; then err "REFUSING: $AK is missing or empty." err " Disabling password auth now would lock you out permanently." err " Install your public key first, then re-run." exit 1 fi KEYCOUNT=$(grep -cvE '^\s*(#|$)' "$AK" || true) ok "$ADMIN_USER has $KEYCOUNT authorized key(s)" # A key on disk does not prove key auth actually works. Make the human check. if [ "$DRY_RUN" != "1" ]; then warn "Have you logged in to THIS box using that key (not a password)?" read -r -p "type 'yes' to continue: " a [ "$a" = "yes" ] || { err "aborted"; exit 1; } fi head1 "Writing hardened sshd config" write_file "$DROPIN" 0600 <<EOF # Managed by harden-3-ssh.sh. # A drop-in, so the distro sshd_config stays pristine and this is easy to revert. PermitRootLogin no PasswordAuthentication no KbdInteractiveAuthentication no ChallengeResponseAuthentication no PubkeyAuthentication yes PermitEmptyPasswords no AuthenticationMethods publickey MaxAuthTries 3 MaxSessions 10 LoginGraceTime 30 AllowUsers ${ADMIN_USER} X11Forwarding no AllowAgentForwarding no AllowTcpForwarding no PermitUserEnvironment no ClientAliveInterval 300 ClientAliveCountMax 2 LogLevel VERBOSE EOF head1 "Validating config" if sshd -t 2>/dev/null; then ok "sshd -t passes" else err "sshd -t FAILED. Reverting the drop-in and changing nothing." sshd -t 2>&1 | sed 's/^/ /' rm -f "$DROPIN" exit 1 fi if [ "$DRY_RUN" = "1" ]; then info "would back up config, arm rollback, reload sshd" summary; exit 0 fi head1 "Arming rollback and reloading" BACKUP="/root/sshd-rollback-$(date +%s)" mkdir -p "$BACKUP" cp -a "$SSHD_CONF" "$BACKUP/sshd_config" [ -d /etc/ssh/sshd_config.d ] && cp -a /etc/ssh/sshd_config.d "$BACKUP/sshd_config.d" did "backed up ssh config to $BACKUP" cat > "$ROLLBACK_BIN" <<EOF #!/bin/sh # Written by harden-3-ssh.sh. Restores SSH to its pre-hardening state. rm -f "$DROPIN" cp -a "$BACKUP/sshd_config" "$SSHD_CONF" [ -d "$BACKUP/sshd_config.d" ] && cp -a "$BACKUP/sshd_config.d/." /etc/ssh/sshd_config.d/ systemctl reload ssh 2>/dev/null || systemctl reload sshd 2>/dev/null logger -t ssh-harden-rollback "SSH config rolled back automatically" EOF chmod 700 "$ROLLBACK_BIN" did "created $ROLLBACK_BIN" rollback_arm ssh-harden-rollback "$ROLLBACK_MIN" "$ROLLBACK_BIN" if systemctl reload ssh 2>/dev/null || systemctl reload sshd 2>/dev/null; then did "sshd reloaded" else err "reload failed; running rollback now" "$ROLLBACK_BIN" exit 1 fi # Existing sessions survive a reload, so this session proves nothing. if confirm_still_connected "ssh ${ADMIN_USER}@<this-box> from a NEW terminal. It must succeed."; then rollback_disarm ssh-harden-rollback ok "SSH hardening confirmed" info "Manual rollback remains available: $ROLLBACK_BIN" else warn "NOT confirmed. Doing nothing — rollback fires in <=${ROLLBACK_MIN} min." warn " Or from the provider's console: $ROLLBACK_BIN" fi summary