|
Bogdan Timofte
authored
2 weeks ago
|
1
|
#!/usr/bin/env bash
|
|
|
2
|
set -euo pipefail
|
|
|
3
|
|
|
|
4
|
real_ssh="/usr/bin/ssh"
|
|
|
5
|
ssh_config="$HOME/.ssh/config"
|
|
|
6
|
remote_agent="/run/user/0/gnupg/S.gpg-agent.ssh"
|
|
|
7
|
|
|
|
8
|
# Access model, May 2026
|
|
|
9
|
# ----------------------
|
|
|
10
|
#
|
|
|
11
|
# Cheia fizica este montata pe is-jumper. Wrapper-ul local nu mai face bridge
|
|
|
12
|
# de agent in /tmp; in schimb ruleaza clientul SSH activ pe is-jumper:
|
|
|
13
|
#
|
|
|
14
|
# local ssh wrapper
|
|
|
15
|
# -> is-jumper (192.168.2.100, acces local cu id_ed25519)
|
|
|
16
|
# -> J1/J2/j1/j2 (autentificare cu agentul fizic de pe is-jumper)
|
|
|
17
|
# -> ssh final-host
|
|
|
18
|
#
|
|
|
19
|
# Custom jump flags (stripped by wrapper, not passed to real ssh):
|
|
|
20
|
# -J1 use J1 via VPN (default for configured hosts)
|
|
|
21
|
# -J2 use J2 via VPN
|
|
|
22
|
# -j1 use j1 via public DNS (urgente, fara ruta VPN)
|
|
|
23
|
# -j2 use j2 via public DNS (urgente, fara ruta VPN)
|
|
|
24
|
#
|
|
|
25
|
# Hosturi arbitrare (fara config SSH): wrapper-ul le ruteza prin jump doar daca
|
|
|
26
|
# unul dintre flagurile de mai sus este prezent explicit.
|
|
|
27
|
|
|
|
28
|
target_user=""
|
|
|
29
|
target_host=""
|
|
|
30
|
target_port=""
|
|
|
31
|
target_auth=""
|
|
|
32
|
found_target=0
|
|
|
33
|
cmd_args=()
|
|
|
34
|
ssh_config_args=()
|
|
|
35
|
jump_alias="j1"
|
|
|
36
|
custom_jump_set=0
|
|
|
37
|
host_configured=1
|
|
|
38
|
target_is_jump=0
|
|
|
39
|
want_subsystem=0
|
|
|
40
|
user_option=""
|
|
|
41
|
port_option=""
|
|
|
42
|
|
|
|
43
|
has_explicit_config() {
|
|
|
44
|
local arg
|
|
|
45
|
|
|
|
46
|
for arg in "$@"; do
|
|
|
47
|
case "$arg" in
|
|
|
48
|
-F|-F*)
|
|
|
49
|
return 0
|
|
|
50
|
;;
|
|
|
51
|
esac
|
|
|
52
|
done
|
|
|
53
|
|
|
|
54
|
return 1
|
|
|
55
|
}
|
|
|
56
|
|
|
|
57
|
quote_cmd() {
|
|
|
58
|
local out="" q part
|
|
|
59
|
|
|
|
60
|
for part in "$@"; do
|
|
|
61
|
printf -v q "%q" "$part"
|
|
|
62
|
out+="$q "
|
|
|
63
|
done
|
|
|
64
|
|
|
|
65
|
printf "%s" "$out"
|
|
|
66
|
}
|
|
|
67
|
|
|
|
68
|
resolve_ssh_config() {
|
|
|
69
|
local target=$1
|
|
|
70
|
local line
|
|
|
71
|
|
|
|
72
|
target_user=""
|
|
|
73
|
target_host=""
|
|
|
74
|
target_port=""
|
|
|
75
|
target_auth=""
|
|
|
76
|
|
|
|
77
|
while IFS= read -r line; do
|
|
|
78
|
case "$line" in
|
|
|
79
|
user\ *) target_user=${line#user } ;;
|
|
|
80
|
hostname\ *) target_host=${line#hostname } ;;
|
|
|
81
|
port\ *) target_port=${line#port } ;;
|
|
|
82
|
setenv\ *NG_SSH_AUTH=password-interactive*) target_auth="password_interactive" ;;
|
|
|
83
|
esac
|
|
|
84
|
done < <("$real_ssh" ${ssh_config_args[@]+"${ssh_config_args[@]}"} -G "$target" 2>/dev/null)
|
|
|
85
|
|
|
|
86
|
[[ -n "$target_user" && -n "$target_host" && -n "$target_port" ]]
|
|
|
87
|
}
|
|
|
88
|
|
|
|
89
|
run_real_ssh() {
|
|
|
90
|
exec "$real_ssh" ${ssh_config_args[@]+"${ssh_config_args[@]}"} "$@"
|
|
|
91
|
}
|
|
|
92
|
|
|
|
93
|
resolve_target_from_config() {
|
|
|
94
|
local target=$1
|
|
|
95
|
local default_user=${USER:-${LOGNAME:-}}
|
|
|
96
|
local user_override=""
|
|
Bogdan Timofte
authored
2 weeks ago
|
97
|
local target_route=""
|
|
Bogdan Timofte
authored
2 weeks ago
|
98
|
|
|
|
99
|
case "$target" in
|
|
|
100
|
*@*)
|
|
|
101
|
user_override=${target%@*}
|
|
|
102
|
target=${target#*@}
|
|
|
103
|
;;
|
|
|
104
|
esac
|
|
|
105
|
|
|
|
106
|
case "$target" in
|
|
|
107
|
is-jumper|192.168.2.100)
|
|
|
108
|
return 1
|
|
|
109
|
;;
|
|
|
110
|
J1|J2|j1|j2)
|
|
|
111
|
target_is_jump=1
|
|
|
112
|
;;
|
|
|
113
|
esac
|
|
|
114
|
|
|
|
115
|
resolve_ssh_config "$target" || return 1
|
|
|
116
|
|
|
Bogdan Timofte
authored
2 weeks ago
|
117
|
# Check for SSH_ROUTE in config
|
|
|
118
|
while IFS= read -r line; do
|
|
|
119
|
case "$line" in
|
|
|
120
|
setenv\ SSH_ROUTE=*)
|
|
|
121
|
target_route=${line#setenv SSH_ROUTE=}
|
|
|
122
|
;;
|
|
|
123
|
esac
|
|
|
124
|
done < <("$real_ssh" ${ssh_config_args[@]+"${ssh_config_args[@]}"} -G "$target" 2>/dev/null)
|
|
|
125
|
|
|
|
126
|
# If route is "local", no jump needed
|
|
|
127
|
if [[ "$target_route" == "local" ]]; then
|
|
|
128
|
return 1
|
|
|
129
|
fi
|
|
|
130
|
|
|
Bogdan Timofte
authored
2 weeks ago
|
131
|
if [[ -n "$user_override" ]]; then
|
|
|
132
|
target_user=$user_override
|
|
|
133
|
fi
|
|
|
134
|
if [[ -n "$user_option" ]]; then
|
|
|
135
|
target_user=$user_option
|
|
|
136
|
fi
|
|
|
137
|
if [[ -n "$port_option" ]]; then
|
|
|
138
|
target_port=$port_option
|
|
|
139
|
fi
|
|
|
140
|
|
|
|
141
|
# Unconfigured host (ssh -G returns defaults): bypass unless a custom jump
|
|
|
142
|
# was requested explicitly.
|
|
|
143
|
if [[ "$target_is_jump" -eq 0 && "$target_host" == "$target" && "$target_port" == "22" && "$target_user" == "$default_user" ]]; then
|
|
|
144
|
[[ $custom_jump_set -eq 0 ]] && return 1
|
|
|
145
|
host_configured=0
|
|
|
146
|
fi
|
|
|
147
|
|
|
|
148
|
[[ -n "$target_user" && -n "$target_host" && -n "$target_port" ]]
|
|
|
149
|
}
|
|
|
150
|
|
|
|
151
|
resolve_jump() {
|
|
|
152
|
local saved_user saved_host saved_port saved_auth
|
|
|
153
|
|
|
|
154
|
saved_user=$target_user
|
|
|
155
|
saved_host=$target_host
|
|
|
156
|
saved_port=$target_port
|
|
|
157
|
saved_auth=$target_auth
|
|
|
158
|
|
|
|
159
|
resolve_ssh_config "$jump_alias" || {
|
|
|
160
|
printf "ssh-wrapper: cannot resolve jump alias %s\n" "$jump_alias" >&2
|
|
|
161
|
exit 255
|
|
|
162
|
}
|
|
|
163
|
|
|
|
164
|
jump_user=$target_user
|
|
|
165
|
jump_host=$target_host
|
|
|
166
|
jump_port=$target_port
|
|
|
167
|
|
|
|
168
|
target_user=$saved_user
|
|
|
169
|
target_host=$saved_host
|
|
|
170
|
target_port=$saved_port
|
|
|
171
|
target_auth=$saved_auth
|
|
|
172
|
}
|
|
|
173
|
|
|
|
174
|
# Pre-process: extract custom jump flags and strip them from args.
|
|
|
175
|
filtered_args=()
|
|
|
176
|
for arg in "$@"; do
|
|
|
177
|
case "$arg" in
|
|
|
178
|
-J1) jump_alias="j1"; custom_jump_set=1 ;;
|
|
|
179
|
-J2) jump_alias="j2"; custom_jump_set=1 ;;
|
|
|
180
|
-j1) jump_alias="j1"; custom_jump_set=1 ;;
|
|
|
181
|
-j2) jump_alias="j2"; custom_jump_set=1 ;;
|
|
|
182
|
*) filtered_args+=("$arg") ;;
|
|
|
183
|
esac
|
|
|
184
|
done
|
|
|
185
|
set -- "${filtered_args[@]+"${filtered_args[@]}"}"
|
|
|
186
|
|
|
|
187
|
if [[ -f "$ssh_config" ]] && ! has_explicit_config "$@"; then
|
|
|
188
|
ssh_config_args=(-F "$ssh_config")
|
|
|
189
|
fi
|
|
|
190
|
|
|
|
191
|
skip_next=0
|
|
|
192
|
capture_user=0
|
|
|
193
|
capture_port=0
|
|
|
194
|
after_double_dash=0
|
|
|
195
|
|
|
|
196
|
for arg in "$@"; do
|
|
|
197
|
if [[ $found_target -eq 1 ]]; then
|
|
|
198
|
cmd_args+=("$arg")
|
|
|
199
|
continue
|
|
|
200
|
fi
|
|
|
201
|
|
|
|
202
|
if [[ $capture_user -eq 1 ]]; then
|
|
|
203
|
user_option=$arg
|
|
|
204
|
capture_user=0
|
|
|
205
|
continue
|
|
|
206
|
fi
|
|
|
207
|
|
|
|
208
|
if [[ $capture_port -eq 1 ]]; then
|
|
|
209
|
port_option=$arg
|
|
|
210
|
capture_port=0
|
|
|
211
|
continue
|
|
|
212
|
fi
|
|
|
213
|
|
|
|
214
|
if [[ $skip_next -eq 1 ]]; then
|
|
|
215
|
skip_next=0
|
|
|
216
|
continue
|
|
|
217
|
fi
|
|
|
218
|
|
|
|
219
|
case "$arg" in
|
|
|
220
|
-G|-Q|-V|-h|--help)
|
|
|
221
|
run_real_ssh "$@"
|
|
|
222
|
;;
|
|
|
223
|
--)
|
|
|
224
|
after_double_dash=1
|
|
|
225
|
continue
|
|
|
226
|
;;
|
|
|
227
|
esac
|
|
|
228
|
|
|
|
229
|
if [[ $after_double_dash -eq 0 ]]; then
|
|
|
230
|
case "$arg" in
|
|
|
231
|
-s)
|
|
|
232
|
want_subsystem=1
|
|
|
233
|
continue
|
|
|
234
|
;;
|
|
|
235
|
-l)
|
|
|
236
|
capture_user=1
|
|
|
237
|
continue
|
|
|
238
|
;;
|
|
|
239
|
-l*)
|
|
|
240
|
user_option=${arg#-l}
|
|
|
241
|
continue
|
|
|
242
|
;;
|
|
|
243
|
-p)
|
|
|
244
|
capture_port=1
|
|
|
245
|
continue
|
|
|
246
|
;;
|
|
|
247
|
-p*)
|
|
|
248
|
port_option=${arg#-p}
|
|
|
249
|
continue
|
|
|
250
|
;;
|
|
|
251
|
-b|-c|-D|-E|-e|-F|-I|-i|-J|-L|-m|-O|-o|-Q|-R|-S|-W|-w)
|
|
|
252
|
skip_next=1
|
|
|
253
|
continue
|
|
|
254
|
;;
|
|
|
255
|
-b*|-c*|-D*|-E*|-e*|-F*|-I*|-i*|-J*|-L*|-m*|-O*|-o*|-Q*|-R*|-S*|-W*|-w*)
|
|
|
256
|
continue
|
|
|
257
|
;;
|
|
|
258
|
-*)
|
|
|
259
|
continue
|
|
|
260
|
;;
|
|
|
261
|
esac
|
|
|
262
|
fi
|
|
|
263
|
|
|
|
264
|
if ! resolve_target_from_config "$arg"; then
|
|
|
265
|
run_real_ssh "$@"
|
|
|
266
|
fi
|
|
|
267
|
|
|
|
268
|
found_target=1
|
|
|
269
|
done
|
|
|
270
|
|
|
|
271
|
if [[ $found_target -eq 0 ]]; then
|
|
|
272
|
run_real_ssh "$@"
|
|
|
273
|
fi
|
|
|
274
|
|
|
|
275
|
tty_flag="-tt"
|
|
|
276
|
if [[ ${#cmd_args[@]} -gt 0 || $want_subsystem -eq 1 ]]; then
|
|
|
277
|
tty_flag="-T"
|
|
|
278
|
fi
|
|
|
279
|
|
|
|
280
|
if [[ $target_is_jump -eq 1 ]]; then
|
|
|
281
|
jump_cmd=(ssh "$tty_flag" -A -o BatchMode=yes -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new -p "$target_port" "$target_user@$target_host")
|
|
|
282
|
jump_cmd+=(${cmd_args[@]+"${cmd_args[@]}"})
|
|
|
283
|
else
|
|
|
284
|
resolve_jump
|
|
|
285
|
|
|
|
286
|
final_cmd=(ssh "$tty_flag" -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new -o ProxyJump=none -o ProxyCommand=none)
|
|
|
287
|
if [[ "$target_auth" == "password_interactive" ]]; then
|
|
|
288
|
final_cmd+=( -o BatchMode=no -o PreferredAuthentications=keyboard-interactive,password -o PubkeyAuthentication=no )
|
|
|
289
|
elif [[ $host_configured -eq 1 ]]; then
|
|
|
290
|
final_cmd+=( -o BatchMode=yes )
|
|
|
291
|
fi
|
|
|
292
|
if [[ $want_subsystem -eq 1 ]]; then
|
|
|
293
|
final_cmd+=( -s )
|
|
|
294
|
fi
|
|
|
295
|
final_cmd+=( -p "$target_port" "$target_user@$target_host" )
|
|
|
296
|
final_cmd+=(${cmd_args[@]+"${cmd_args[@]}"})
|
|
|
297
|
|
|
|
298
|
jump_cmd=(ssh "$tty_flag" -A -o BatchMode=yes -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new -p "$jump_port" "$jump_user@$jump_host" "$(quote_cmd "${final_cmd[@]}")")
|
|
|
299
|
fi
|
|
|
300
|
|
|
|
301
|
is_jumper_cmd="SSH_AUTH_SOCK=$remote_agent exec $(quote_cmd "${jump_cmd[@]}")"
|
|
|
302
|
|
|
|
303
|
exec "$real_ssh" ${ssh_config_args[@]+"${ssh_config_args[@]}"} "$tty_flag" -o BatchMode=yes -o ConnectTimeout=10 is-jumper "$is_jumper_cmd"
|