Skip to content

Commit 48a71cc

Browse files
LTSCommerceclaude
andcommitted
feat(ssh-helpers): preset GIT_SSH_COMMAND on interactive -A logins
On an interactive forwarded login (ssh-with-password/-key with -A and no remote command), start the remote shell with GIT_SSH_COMMAND='ssh -F /dev/null' so 'git push' over there uses the forwarded agent key. Without it, a machine whose repo-managed ~/.ssh/config pins 'IdentitiesOnly yes' to a local, push-less key (e.g. ~/.ssh/id) shadows the forwarded key and the push is denied. -F /dev/null reads no config, so only the forwarded key is offered; the custom-named local key is not a default identity so it is not picked up. Adds two helpers to ssh-with-agent.bash: swa_has_remote_command (an ssh-arg parser that tells an interactive login from a passed command, so a supplied remote command still runs verbatim) and swa_remote_git_push_init (emits the remote init line). scp wrappers are left untouched — there is no interactive shell to inject into. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 2536b14 commit 48a71cc

3 files changed

Lines changed: 77 additions & 6 deletions

File tree

files/home/.local/bin/ssh-with-agent.bash

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,57 @@ swa_wants_agent_forward() {
2525
return 1
2626
}
2727

28+
# swa_remote_git_push_init — print the remote-shell init run on an interactive
29+
# forwarded login. It presets GIT_SSH_COMMAND so `git push` on the remote uses
30+
# the FORWARDED agent and ignores that machine's ~/.ssh/config (its
31+
# `IdentitiesOnly yes` + custom-named key would otherwise shadow the forwarded
32+
# key and authenticate as the wrong, push-less account). `-F /dev/null` reads no
33+
# config, so only the forwarded agent key is offered, then a login shell is
34+
# exec'd. The \$SHELL is deliberately left for the REMOTE shell to expand.
35+
swa_remote_git_push_init() {
36+
printf '%s' "export GIT_SSH_COMMAND=\"ssh -F /dev/null\"; exec \"\$SHELL\" -l"
37+
}
38+
39+
# swa_has_remote_command ARGS... — succeed (0) when the ssh args include a remote
40+
# command (a non-option token after the destination), i.e. NOT an interactive
41+
# login. Mirrors ssh's own option parsing: the short options below take a
42+
# following argument, everything else is a boolean flag or the destination.
43+
swa_has_remote_command() {
44+
local argopts="bBcDEeFIiJLlmOopQRSWw"
45+
local t cl ch n i skip_next=0 seen_host=0
46+
for t in "$@"; do
47+
if [ "$skip_next" -eq 1 ]; then
48+
skip_next=0
49+
continue
50+
fi
51+
case "$t" in
52+
-*)
53+
cl="${t#-}"
54+
n=${#cl}
55+
i=0
56+
while [ "$i" -lt "$n" ]; do
57+
ch="${cl:$i:1}"
58+
if [[ "$argopts" == *"$ch"* ]]; then
59+
# Arg-taking option: it consumes the rest of this token as
60+
# its value, or the next token when it is the cluster's tail.
61+
[ "$i" -eq $((n - 1)) ] && skip_next=1
62+
break
63+
fi
64+
i=$((i + 1))
65+
done
66+
;;
67+
*)
68+
if [ "$seen_host" -eq 0 ]; then
69+
seen_host=1
70+
else
71+
return 0
72+
fi
73+
;;
74+
esac
75+
done
76+
return 1
77+
}
78+
2879
# swa_discover_keys — print candidate private keys in SWA_SSH_DIR, one per line.
2980
# Skips public keys and the usual non-key files.
3081
swa_discover_keys() {

files/home/.local/bin/ssh-with-key

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,11 @@
1111
# Agent forwarding: pass -A (or -o ForwardAgent=yes) and a fresh, dedicated
1212
# ssh-agent is started for this session — you are asked which keys to add — and
1313
# forwarded to the remote host. The agent is killed again when the connection
14-
# closes. Auth to THIS host is unaffected: it still uses only the -i key because
15-
# IdentitiesOnly=yes ignores every agent-held identity.
14+
# closes. For an interactive login (no remote command given) the remote shell is
15+
# started with GIT_SSH_COMMAND preset, so `git push` over there uses the
16+
# forwarded key even on a machine whose ~/.ssh/config pins IdentitiesOnly to a
17+
# local, push-less key. Auth to THIS host is unaffected: it still uses only the
18+
# -i key because IdentitiesOnly=yes ignores every agent-held identity.
1619
#
1720
# Usage:
1821
# ssh-with-key [-i KEYPATH] [ssh-args...] user@host
@@ -121,7 +124,14 @@ if swa_wants_agent_forward "$@"; then
121124
# NOT exec — running ssh as a child lets the EXIT trap kill the agent after.
122125
swa_start_ephemeral_agent
123126
rc=0
124-
ssh "${opts[@]}" "$@" || rc=$?
127+
if swa_has_remote_command "$@"; then
128+
# User supplied their own remote command — just forward the agent.
129+
ssh "${opts[@]}" "$@" || rc=$?
130+
else
131+
# Interactive login: preset GIT_SSH_COMMAND on the remote so `git push`
132+
# there uses the forwarded agent (-t for a real shell, see the lib note).
133+
ssh -t "${opts[@]}" "$@" "$(swa_remote_git_push_init)" || rc=$?
134+
fi
125135
exit "$rc"
126136
fi
127137

files/home/.local/bin/ssh-with-password

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,11 @@
1717
# Agent forwarding: pass -A (or -o ForwardAgent=yes) and a fresh, dedicated
1818
# ssh-agent is started for this session — you are asked which keys to add — and
1919
# forwarded to the remote host. The agent is killed again when the connection
20-
# closes. Auth to THIS host is unaffected: it stays password-only because
21-
# PubkeyAuthentication=no blocks every key regardless of the forwarded agent.
20+
# closes. For an interactive login (no remote command given) the remote shell is
21+
# started with GIT_SSH_COMMAND preset, so `git push` over there uses the
22+
# forwarded key even on a machine whose ~/.ssh/config pins IdentitiesOnly to a
23+
# local, push-less key. Auth to THIS host is unaffected: it stays password-only
24+
# because PubkeyAuthentication=no blocks every key regardless of the agent.
2225
set -euo pipefail
2326

2427
if [ "$#" -eq 0 ] || [ "${1:-}" = "--help" ] || [ "${1:-}" = "-h" ]; then
@@ -56,7 +59,14 @@ if swa_wants_agent_forward "$@"; then
5659
# NOT exec — running ssh as a child lets the EXIT trap kill the agent after.
5760
swa_start_ephemeral_agent
5861
rc=0
59-
ssh "${opts[@]}" "$@" || rc=$?
62+
if swa_has_remote_command "$@"; then
63+
# User supplied their own remote command — just forward the agent.
64+
ssh "${opts[@]}" "$@" || rc=$?
65+
else
66+
# Interactive login: preset GIT_SSH_COMMAND on the remote so `git push`
67+
# there uses the forwarded agent (-t for a real shell, see the lib note).
68+
ssh -t "${opts[@]}" "$@" "$(swa_remote_git_push_init)" || rc=$?
69+
fi
6070
exit "$rc"
6171
fi
6272

0 commit comments

Comments
 (0)