Add SSH key revocation to offboard flow
On offboard, neither server should retain SSH access to the other.
Keys are now revoked as the final step before Tailscale removal — after
all state pushes and syncs are complete so no SSH operation is cut short.
Revocation runs in both owner-initiated offboard and when --check
finalises a mirror-requested offboard. Both directions are handled:
- Our pubkey removed from remote's authorized_keys via SSH (identified
by key comment keyname@hostname set by ssh_setup.sh at keygen time)
- Remote's pubkey removed from local authorized_keys (matched by
@REMOTE_SERVER_NAME suffix — unique across the authorized_keys file)
Non-fatal: SSH failure logs a manual revocation instruction; local
cleanup still runs. Summary shows per-direction revocation status.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
4821dbdfb4
commit
6674dd4c17
@@ -384,6 +384,72 @@ remove_from_blocklist() {
|
||||
log "Removed from blocklist: $hostname"
|
||||
}
|
||||
|
||||
# Revoke SSH access on both sides — call after all other SSH operations complete.
|
||||
# Matches by key comment (format: keyname@hostname — set by ssh_setup.sh at keygen time).
|
||||
# SSH_REVOKE_REMOTE_OK / SSH_REVOKE_LOCAL_OK set in caller scope for summary display.
|
||||
do_ssh_key_revocation() {
|
||||
local remote_ip="$1"
|
||||
local pub_key_file="${SSH_KEY}.pub"
|
||||
|
||||
echo ""
|
||||
echo "━━━ $ICON_SHIELD SSH Key Revocation ━━━"
|
||||
|
||||
# Step 1: remove our pubkey from remote's authorized_keys while SSH still works
|
||||
SSH_REVOKE_REMOTE_OK=false
|
||||
if [[ -f "$pub_key_file" ]]; then
|
||||
local our_comment
|
||||
our_comment=$(awk '{print $3}' "$pub_key_file" 2>/dev/null)
|
||||
if [[ -n "$our_comment" ]]; then
|
||||
log "Revoking our pubkey ($our_comment) from $REMOTE_SERVER_NAME..."
|
||||
if [[ "$DRY_RUN" == true ]]; then
|
||||
warn "DRY RUN — would remove our pubkey from $REMOTE_SERVER_NAME authorized_keys"
|
||||
SSH_REVOKE_REMOTE_OK=true
|
||||
elif [[ -n "$remote_ip" ]] && timeout "$SSH_TIMEOUT" ssh -i "$SSH_KEY" \
|
||||
-o ConnectTimeout="$SSH_TIMEOUT" -o BatchMode=yes root@"$remote_ip" \
|
||||
"grep -v '${our_comment}' /root/.ssh/authorized_keys \
|
||||
> /root/.ssh/authorized_keys.tmp 2>/dev/null \
|
||||
&& mv /root/.ssh/authorized_keys.tmp /root/.ssh/authorized_keys \
|
||||
&& echo removed" 2>/dev/null | grep -q removed; then
|
||||
log "Our pubkey revoked from $REMOTE_SERVER_NAME ✅"
|
||||
SSH_REVOKE_REMOTE_OK=true
|
||||
else
|
||||
warn "Remote revocation failed — revoke manually on $REMOTE_SERVER_NAME:"
|
||||
warn " grep -v '@${LOCAL_SERVER_NAME}' /root/.ssh/authorized_keys > /root/.ssh/authorized_keys"
|
||||
fi
|
||||
else
|
||||
warn "Could not read pubkey comment from $pub_key_file — skipping remote revocation"
|
||||
fi
|
||||
else
|
||||
warn "Pubkey not found at $pub_key_file — skipping remote revocation"
|
||||
fi
|
||||
|
||||
# Step 2: remove remote's pubkey from our local authorized_keys
|
||||
# Remote key comment ends with @REMOTE_SERVER_NAME — unique, no special chars
|
||||
SSH_REVOKE_LOCAL_OK=false
|
||||
log "Revoking $REMOTE_SERVER_NAME pubkey from local authorized_keys..."
|
||||
if [[ "$DRY_RUN" == true ]]; then
|
||||
warn "DRY RUN — would remove $REMOTE_SERVER_NAME pubkey from local authorized_keys"
|
||||
SSH_REVOKE_LOCAL_OK=true
|
||||
elif [[ -f /root/.ssh/authorized_keys ]]; then
|
||||
if grep -q "@${REMOTE_SERVER_NAME}" /root/.ssh/authorized_keys 2>/dev/null; then
|
||||
if grep -v "@${REMOTE_SERVER_NAME}" /root/.ssh/authorized_keys \
|
||||
> /root/.ssh/authorized_keys.tmp 2>/dev/null && \
|
||||
mv /root/.ssh/authorized_keys.tmp /root/.ssh/authorized_keys; then
|
||||
log "$REMOTE_SERVER_NAME pubkey revoked locally ✅"
|
||||
SSH_REVOKE_LOCAL_OK=true
|
||||
else
|
||||
warn "Failed to update local authorized_keys — remove @${REMOTE_SERVER_NAME} entry manually"
|
||||
fi
|
||||
else
|
||||
log "$REMOTE_SERVER_NAME pubkey not found in local authorized_keys — already removed"
|
||||
SSH_REVOKE_LOCAL_OK=true
|
||||
fi
|
||||
else
|
||||
log "/root/.ssh/authorized_keys not found — nothing to remove locally"
|
||||
SSH_REVOKE_LOCAL_OK=true
|
||||
fi
|
||||
}
|
||||
|
||||
# ── FolderView3 integration ───────────────────────────────────────────────────────────────────
|
||||
FOLDERVIEW3_DIR="/usr/local/emhttp/plugins/folder.view3"
|
||||
FOLDERVIEW3_JSON="/boot/config/plugins/folder.view3/docker.json"
|
||||
@@ -826,6 +892,12 @@ if [[ "$MODE" == "check" ]]; then
|
||||
update_master_conf "CRITICAL_RSYNC_ENABLED" "false"
|
||||
fi
|
||||
|
||||
# SSH key revocation — mutual, both directions
|
||||
# REMOTE_IP already resolved above — this is the last SSH operation
|
||||
SSH_REVOKE_REMOTE_OK=false
|
||||
SSH_REVOKE_LOCAL_OK=false
|
||||
do_ssh_key_revocation "$REMOTE_IP"
|
||||
|
||||
if [[ "${PARTNERSHIP_REMOVE_TAILSCALE:-true}" == true ]]; then
|
||||
local grace_seconds=$(( ${PARTNERSHIP_GRACE_HOURS:-6} * 3600 ))
|
||||
warn "Waiting ${PARTNERSHIP_GRACE_HOURS:-6}hr grace period..."
|
||||
@@ -1135,6 +1207,12 @@ if [[ "$MODE" == "offboard" ]]; then
|
||||
folderview3_remove_partner_folder "$PARTNER_FOLDER_NAME"
|
||||
fi
|
||||
|
||||
# SSH key revocation — mutual, both directions
|
||||
# Must run before Tailscale removal (SSH needs network) and after state is pushed
|
||||
SSH_REVOKE_REMOTE_OK=false
|
||||
SSH_REVOKE_LOCAL_OK=false
|
||||
do_ssh_key_revocation "${MIRROR_IP:-}"
|
||||
|
||||
# Tailscale removal
|
||||
if [[ "${PARTNERSHIP_REMOVE_TAILSCALE:-true}" == true ]]; then
|
||||
echo ""
|
||||
@@ -1174,6 +1252,16 @@ if [[ "$MODE" == "offboard" ]]; then
|
||||
echo " Critical rsync: disabled ✅"
|
||||
echo " State: INACTIVE ✅"
|
||||
echo " Blocklist: $MIRROR blocked — re-onboard to permit access again ✅"
|
||||
_revoke_status() {
|
||||
if [[ "${SSH_REVOKE_REMOTE_OK:-false}" == true ]] && [[ "${SSH_REVOKE_LOCAL_OK:-false}" == true ]]; then
|
||||
echo "both directions ✅"
|
||||
elif [[ "${SSH_REVOKE_LOCAL_OK:-false}" == true ]]; then
|
||||
echo "local only ✅ — remote failed (revoke manually on $MIRROR)"
|
||||
else
|
||||
echo "⚠️ failed — check warnings above"
|
||||
fi
|
||||
}
|
||||
echo " Keys revoked: $(_revoke_status)"
|
||||
[[ "${PARTNERSHIP_FOLDERVIEW3:-false}" == true ]] && \
|
||||
echo " FolderView3: ${PARTNER_FOLDER_NAME:-} cleaned ✅"
|
||||
[[ "${PARTNERSHIP_REMOVE_TAILSCALE:-true}" == true ]] && \
|
||||
|
||||
Reference in New Issue
Block a user