xref: /freebsd/crypto/openssh/regress/agent-restrict.sh (revision 644b4646c7acab87dc20d4e5dd53d2d9da152989)
1#	$OpenBSD: agent-restrict.sh,v 1.8 2025/05/23 08:40:13 dtucker Exp $
2#	Placed in the Public Domain.
3
4tid="agent restrictions"
5
6SSH_AUTH_SOCK="$OBJ/agent.sock"
7export SSH_AUTH_SOCK
8rm -f $SSH_AUTH_SOCK $OBJ/agent.log $OBJ/host_[abcdex]* $OBJ/user_[abcdex]*
9rm -f $OBJ/sshd_proxy_host* $OBJ/ssh_output* $OBJ/expect_*
10rm -f $OBJ/ssh_proxy[._]* $OBJ/command
11
12verbose "generate keys"
13for h in a b c d e x ca ; do
14	$SSHKEYGEN -q -t ed25519 -C host_$h -N '' -f $OBJ/host_$h || \
15		fatal "ssh-keygen hostkey failed"
16	$SSHKEYGEN -q -t ed25519 -C user_$h -N '' -f $OBJ/user_$h || \
17		fatal "ssh-keygen userkey failed"
18done
19
20# Make some hostcerts
21for h in d e ; do
22	id="host_$h"
23	$SSHKEYGEN -q -s $OBJ/host_ca -I $id -n $id -h $OBJ/host_${h}.pub || \
24		fatal "ssh-keygen certify failed"
25done
26
27verbose "prepare client config"
28egrep -vi '(identityfile|hostname|hostkeyalias|proxycommand)' \
29	$OBJ/ssh_proxy > $OBJ/ssh_proxy.bak
30cat << _EOF > $OBJ/ssh_proxy
31IdentitiesOnly yes
32ForwardAgent yes
33ExitOnForwardFailure yes
34_EOF
35cp $OBJ/ssh_proxy $OBJ/ssh_proxy_noid
36for h in a b c d e ; do
37	cat << _EOF >> $OBJ/ssh_proxy
38Host host_$h
39	Hostname host_$h
40	HostkeyAlias host_$h
41	IdentityFile $OBJ/user_$h
42	ProxyCommand ${SUDO} env SSH_SK_HELPER=\"$SSH_SK_HELPER\" ${TEST_SSH_SSHD_ENV} ${OBJ}/sshd-log-wrapper.sh -i -f $OBJ/sshd_proxy_host_$h
43_EOF
44	# Variant with no specified keys.
45	cat << _EOF >> $OBJ/ssh_proxy_noid
46Host host_$h
47	Hostname host_$h
48	HostkeyAlias host_$h
49	ProxyCommand ${SUDO} env SSH_SK_HELPER=\"$SSH_SK_HELPER\" ${TEST_SSH_SSHD_ENV} ${OBJ}/sshd-log-wrapper.sh -i -f $OBJ/sshd_proxy_host_$h
50_EOF
51done
52cat $OBJ/ssh_proxy.bak >> $OBJ/ssh_proxy
53cat $OBJ/ssh_proxy.bak >> $OBJ/ssh_proxy_noid
54
55verbose "prepare known_hosts"
56rm -f $OBJ/known_hosts
57for h in a b c x ; do
58	(printf "host_$h " ; cat $OBJ/host_${h}.pub) >> $OBJ/known_hosts
59done
60(printf "@cert-authority host_* " ; cat $OBJ/host_ca.pub) >> $OBJ/known_hosts
61
62verbose "prepare server configs"
63egrep -vi '(hostkey|pidfile)' $OBJ/sshd_proxy \
64	> $OBJ/sshd_proxy.bak
65for h in a b c d e; do
66	cp $OBJ/sshd_proxy.bak $OBJ/sshd_proxy_host_$h
67	cat << _EOF >> $OBJ/sshd_proxy_host_$h
68ExposeAuthInfo yes
69PidFile none
70Hostkey $OBJ/host_$h
71_EOF
72done
73for h in d e ; do
74	echo "HostCertificate $OBJ/host_${h}-cert.pub" \
75		>> $OBJ/sshd_proxy_host_$h
76done
77# Create authorized_keys with canned command.
78reset_keys() {
79	_whichcmd="$1"
80	_command=""
81	case "$_whichcmd" in
82	authinfo)	_command="cat \$SSH_USER_AUTH" ;;
83	keylist)		_command="$SSHADD -L | cut -d' ' -f-2 | \
84	    env LC_ALL=C sort" ;;
85	*)		fatal "unsupported command $_whichcmd" ;;
86	esac
87	trace "reset keys"
88	>$OBJ/authorized_keys_$USER
89	for h in e d c b a; do
90		(printf "%s" "restrict,agent-forwarding,command=\"$_command\" ";
91		 cat $OBJ/user_$h.pub) >> $OBJ/authorized_keys_$USER
92	done
93}
94# Prepare a key for comparison with ExposeAuthInfo/$SSH_USER_AUTH.
95expect_key() {
96	_key="$OBJ/${1}.pub"
97	_file="$OBJ/$2"
98	(printf "publickey " ; cut -d' ' -f-2 $_key) > $_file
99}
100# Prepare expect_* files to compare against authinfo forced command to ensure
101# keys used for authentication match.
102reset_expect_keys() {
103	for u in a b c d e; do
104		expect_key user_$u expect_$u
105	done
106}
107# ssh to host, expecting success and that output matched expectation for
108# that host (expect_$h file).
109expect_succeed() {
110	_id="$1"
111	_case="$2"
112	shift; shift; _extra="$@"
113	_host="host_$_id"
114	trace "connect $_host expect success"
115	rm -f $OBJ/ssh_output
116	${SSH} $_extra -F $OBJ/ssh_proxy $_host true > $OBJ/ssh_output
117	_s=$?
118	test $_s -eq 0 || fail "host $_host $_case fail, exit status $_s"
119	diff $OBJ/ssh_output $OBJ/expect_${_id} ||
120		fail "unexpected ssh output"
121}
122# ssh to host using explicit key, expecting success and that the key was
123# actually used for authentication.
124expect_succeed_key() {
125	_id="$1"
126	_key="$2"
127	_case="$3"
128	shift; shift; shift; _extra="$@"
129	_host="host_$_id"
130	trace "connect $_host expect success, with key $_key"
131	_keyfile="$OBJ/$_key"
132	rm -f $OBJ/ssh_output
133	${SSH} $_extra -F $OBJ/ssh_proxy_noid \
134	    -oIdentityFile=$_keyfile $_host true > $OBJ/ssh_output
135	_s=$?
136	test $_s -eq 0 || fail "host $_host $_key $_case fail, exit status $_s"
137	expect_key $_key expect_key
138	diff $OBJ/ssh_output $OBJ/expect_key ||
139		fail "incorrect key used for authentication"
140}
141# ssh to a host, expecting it to fail.
142expect_fail() {
143	_host="$1"
144	_case="$2"
145	shift; shift; _extra="$@"
146	trace "connect $_host expect failure"
147	${SSH} $_extra -F $OBJ/ssh_proxy $_host true >/dev/null && \
148		fail "host $_host $_case succeeded unexpectedly"
149}
150# ssh to a host using an explicit key, expecting it to fail.
151expect_fail_key() {
152	_id="$1"
153	_key="$2"
154	_case="$3"
155	shift; shift; shift; _extra="$@"
156	_host="host_$_id"
157	trace "connect $_host expect failure, with key $_key"
158	_keyfile="$OBJ/$_key"
159	${SSH} $_extra -F $OBJ/ssh_proxy_noid -oIdentityFile=$_keyfile \
160	    $_host true > $OBJ/ssh_output && \
161		fail "host $_host $_key $_case succeeded unexpectedly"
162}
163# Move the private key files out of the way to force use of agent-hosted keys.
164hide_privatekeys() {
165	trace "hide private keys"
166	for u in a b c d e x; do
167		mv $OBJ/user_$u $OBJ/user_x$u || fatal "hide privkey $u"
168	done
169}
170# Put the private key files back.
171restore_privatekeys() {
172	trace "restore private keys"
173	for u in a b c d e x; do
174		mv $OBJ/user_x$u $OBJ/user_$u || fatal "restore privkey $u"
175	done
176}
177clear_agent() {
178	${SSHADD} -D > /dev/null 2>&1 || fatal "clear agent failed"
179}
180
181reset_keys authinfo
182reset_expect_keys
183
184verbose "authentication w/o agent"
185for h in a b c d e ; do
186	expect_succeed $h "w/o agent"
187	wrongkey=user_e
188	test "$h" = "e" && wrongkey=user_a
189	expect_succeed_key $h $wrongkey "\"wrong\" key w/o agent"
190done
191hide_privatekeys
192for h in a b c d e ; do
193	expect_fail $h "w/o agent"
194done
195restore_privatekeys
196
197verbose "start agent"
198${SSHAGENT} ${EXTRA_AGENT_ARGS} -d -a $SSH_AUTH_SOCK > $OBJ/agent.log 2>&1 &
199AGENT_PID=$!
200trap "kill $AGENT_PID" EXIT
201sleep 4 # Give it a chance to start
202# Check that it's running.
203${SSHADD} -l > /dev/null 2>&1
204if [ $? -ne 1 ]; then
205	fail "ssh-add -l did not fail with exit code 1"
206fi
207
208verbose "authentication with agent (no restrict)"
209for u in a b c d e x; do
210	$SSHADD -q $OBJ/user_$u || fatal "add key $u unrestricted"
211done
212hide_privatekeys
213for h in a b c d e ; do
214	expect_succeed $h "with agent"
215	wrongkey=user_e
216	test "$h" = "e" && wrongkey=user_a
217	expect_succeed_key $h $wrongkey "\"wrong\" key with agent"
218done
219
220verbose "unrestricted keylist"
221reset_keys keylist
222rm -f $OBJ/expect_list.pre
223# List of keys from agent should contain everything.
224for u in a b c d e x; do
225	cut -d " " -f-2 $OBJ/user_${u}.pub >> $OBJ/expect_list.pre
226done
227env LC_ALL=C sort $OBJ/expect_list.pre > $OBJ/expect_list
228for h in a b c d e; do
229	cp $OBJ/expect_list $OBJ/expect_$h
230	expect_succeed $h "unrestricted keylist"
231done
232restore_privatekeys
233
234verbose "authentication with agent (basic restrict)"
235reset_keys authinfo
236reset_expect_keys
237for h in a b c d e; do
238	$SSHADD -h host_$h -H $OBJ/known_hosts -q $OBJ/user_$h \
239		|| fatal "add key $u basic restrict"
240done
241# One more, unrestricted
242$SSHADD -q $OBJ/user_x || fatal "add unrestricted key"
243hide_privatekeys
244# Authentication to host with expected key should work.
245for h in a b c d e ; do
246	expect_succeed $h "with agent"
247done
248# Authentication to host with incorrect key should fail.
249verbose "authentication with agent incorrect key (basic restrict)"
250for h in a b c d e ; do
251	wrongkey=user_e
252	test "$h" = "e" && wrongkey=user_a
253	expect_fail_key $h $wrongkey "wrong key with agent (basic restrict)"
254done
255
256verbose "keylist (basic restrict)"
257reset_keys keylist
258# List from forwarded agent should contain only user_x - the unrestricted key.
259cut -d " " -f-2 $OBJ/user_x.pub > $OBJ/expect_list
260for h in a b c d e; do
261	cp $OBJ/expect_list $OBJ/expect_$h
262	expect_succeed $h "keylist (basic restrict)"
263done
264restore_privatekeys
265
266verbose "username"
267reset_keys authinfo
268reset_expect_keys
269for h in a b c d e; do
270	$SSHADD -h "${USER}@host_$h" -H $OBJ/known_hosts -q $OBJ/user_$h \
271		|| fatal "add key $u basic restrict"
272done
273hide_privatekeys
274for h in a b c d e ; do
275	expect_succeed $h "wildcard user"
276done
277restore_privatekeys
278
279verbose "username wildcard"
280reset_keys authinfo
281reset_expect_keys
282for h in a b c d e; do
283	$SSHADD -h "*@host_$h" -H $OBJ/known_hosts -q $OBJ/user_$h \
284		|| fatal "add key $u basic restrict"
285done
286hide_privatekeys
287for h in a b c d e ; do
288	expect_succeed $h "wildcard user"
289done
290restore_privatekeys
291
292verbose "username incorrect"
293reset_keys authinfo
294reset_expect_keys
295for h in a b c d e; do
296	$SSHADD -h "--BADUSER@host_$h" -H $OBJ/known_hosts -q $OBJ/user_$h \
297		|| fatal "add key $u basic restrict"
298done
299hide_privatekeys
300for h in a b c d e ; do
301	expect_fail $h "incorrect user"
302done
303restore_privatekeys
304
305
306verbose "agent restriction honours certificate principal"
307reset_keys authinfo
308reset_expect_keys
309clear_agent
310$SSHADD -h host_e -H $OBJ/known_hosts -q $OBJ/user_d || fatal "add key"
311hide_privatekeys
312expect_fail d "restricted agent w/ incorrect cert principal"
313restore_privatekeys
314
315# Prepares the script used to drive chained ssh connections for the
316# multihop tests. Believe me, this is easier than getting the escaping
317# right for 5 hops on the command-line...
318prepare_multihop_script() {
319	MULTIHOP_RUN=$OBJ/command
320	cat << _EOF > $MULTIHOP_RUN
321#!/bin/sh
322#set -x
323me="\$1" ; shift
324next="\$1"
325if test ! -z "\$me" ; then 
326	rm -f $OBJ/done
327	echo "HOSTNAME host_\$me"
328	echo "AUTHINFO"
329	cat \$SSH_USER_AUTH
330fi
331echo AGENT
332$SSHADD -L | egrep "^ssh" | cut -d" " -f-2 | env LC_ALL=C sort
333if test -z "\$next" ; then 
334	touch $OBJ/done
335	echo "FINISH"
336	e=0
337else
338	echo NEXT
339	${SSH} -F $OBJ/ssh_proxy_noid -oIdentityFile=$OBJ/user_a \
340		host_\$next $MULTIHOP_RUN "\$@"
341	e=\$?
342fi
343echo "COMPLETE \"\$me\""
344if test ! -z "\$me" ; then 
345	if test ! -f $OBJ/done ; then
346		echo "DONE MARKER MISSING"
347		test \$e -eq 0 && e=63
348	fi
349fi
350exit \$e
351_EOF
352	chmod u+x $MULTIHOP_RUN
353}
354
355# Prepare expected output for multihop tests at expect_a
356prepare_multihop_expected() {
357	_keys="$1"
358	_hops="a b c d e"
359	test -z "$2" || _hops="$2"
360	_revhops=$(echo "$_hops" | rev)
361	_lasthop=$(echo "$_hops" | sed 's/.* //')
362
363	rm -f $OBJ/expect_keys
364	for h in a b c d e; do
365		cut -d" " -f-2 $OBJ/user_${h}.pub >> $OBJ/expect_keys
366	done
367	rm -f $OBJ/expect_a
368	echo "AGENT" >> $OBJ/expect_a
369	test "x$_keys" = "xnone" || env LC_ALL=C sort $OBJ/expect_keys >> $OBJ/expect_a
370	echo "NEXT" >> $OBJ/expect_a
371	for h in $_hops ; do
372		echo "HOSTNAME host_$h" >> $OBJ/expect_a
373		echo "AUTHINFO" >> $OBJ/expect_a
374		(printf "publickey " ; cut -d" " -f-2 $OBJ/user_a.pub) >> $OBJ/expect_a
375		echo "AGENT" >> $OBJ/expect_a
376		if test "x$_keys" = "xall" ; then
377			env LC_ALL=C sort $OBJ/expect_keys >> $OBJ/expect_a
378		fi
379		if test "x$h" != "x$_lasthop" ; then
380			if test "x$_keys" = "xfiltered" ; then
381				cut -d" " -f-2 $OBJ/user_a.pub >> $OBJ/expect_a
382			fi
383			echo "NEXT" >> $OBJ/expect_a
384		fi
385	done
386	echo "FINISH" >> $OBJ/expect_a
387	for h in $_revhops "" ; do
388		echo "COMPLETE \"$h\"" >> $OBJ/expect_a
389	done
390}
391
392prepare_multihop_script
393cp $OBJ/user_a.pub $OBJ/authorized_keys_$USER # only one key used.
394
395verbose "multihop without agent"
396clear_agent
397prepare_multihop_expected none
398$MULTIHOP_RUN "" a b c d e > $OBJ/ssh_output || fail "multihop no agent ssh failed"
399diff $OBJ/ssh_output $OBJ/expect_a || fail "unexpected ssh output"
400
401verbose "multihop agent unrestricted"
402clear_agent
403$SSHADD -q $OBJ/user_[abcde]
404prepare_multihop_expected all
405$MULTIHOP_RUN "" a b c d e > $OBJ/ssh_output || fail "multihop no agent ssh failed"
406diff $OBJ/ssh_output $OBJ/expect_a || fail "unexpected ssh output"
407
408verbose "multihop restricted"
409clear_agent
410prepare_multihop_expected filtered
411# Add user_a, with permission to connect through the whole chain.
412$SSHADD -h host_a -h "host_a>host_b" -h "host_b>host_c" \
413	-h "host_c>host_d" -h "host_d>host_e" \
414	-H $OBJ/known_hosts -q $OBJ/user_a \
415	|| fatal "add key user_a multihop"
416# Add the other keys, bound to a unused host.
417$SSHADD -q -h host_x -H $OBJ/known_hosts $OBJ/user_[bcde] || fail "add keys"
418hide_privatekeys
419$MULTIHOP_RUN "" a b c d e > $OBJ/ssh_output || fail "multihop ssh failed"
420diff $OBJ/ssh_output $OBJ/expect_a || fail "unexpected ssh output"
421restore_privatekeys
422
423verbose "multihop username"
424$SSHADD -h host_a -h "host_a>${USER}@host_b" -h "host_b>${USER}@host_c" \
425	-h "host_c>${USER}@host_d"  -h "host_d>${USER}@host_e" \
426	-H $OBJ/known_hosts -q $OBJ/user_a || fatal "add key user_a multihop"
427hide_privatekeys
428$MULTIHOP_RUN "" a b c d e > $OBJ/ssh_output || fail "multihop w/ user ssh failed"
429diff $OBJ/ssh_output $OBJ/expect_a || fail "unexpected ssh output"
430restore_privatekeys
431
432verbose "multihop wildcard username"
433$SSHADD -h host_a -h "host_a>*@host_b" -h "host_b>*@host_c" \
434	-h "host_c>*@host_d"  -h "host_d>*@host_e" \
435	-H $OBJ/known_hosts -q $OBJ/user_a || fatal "add key user_a multihop"
436hide_privatekeys
437$MULTIHOP_RUN "" a b c d e > $OBJ/ssh_output || fail "multihop w/ user ssh failed"
438diff $OBJ/ssh_output $OBJ/expect_a || fail "unexpected ssh output"
439restore_privatekeys
440
441verbose "multihop wrong username"
442$SSHADD -h host_a -h "host_a>*@host_b" -h "host_b>*@host_c" \
443	-h "host_c>--BADUSER@host_d"  -h "host_d>*@host_e" \
444	-H $OBJ/known_hosts -q $OBJ/user_a || fatal "add key user_a multihop"
445hide_privatekeys
446$MULTIHOP_RUN "" a b c d e > $OBJ/ssh_output && \
447	fail "multihop with wrong user succeeded unexpectedly"
448restore_privatekeys
449
450verbose "multihop cycle no agent"
451clear_agent
452prepare_multihop_expected none "a b a a c d e"
453$MULTIHOP_RUN "" a b a a c d e > $OBJ/ssh_output || \
454	fail "multihop cycle no-agent fail"
455diff $OBJ/ssh_output $OBJ/expect_a || fail "unexpected ssh output"
456
457verbose "multihop cycle agent unrestricted"
458clear_agent
459$SSHADD -q $OBJ/user_[abcde] || fail "add keys"
460prepare_multihop_expected all "a b a a c d e"
461$MULTIHOP_RUN "" a b a a c d e > $OBJ/ssh_output || \
462	fail "multihop cycle agent ssh failed"
463diff $OBJ/ssh_output $OBJ/expect_a || fail "unexpected ssh output"
464
465verbose "multihop cycle restricted deny"
466clear_agent
467$SSHADD -q -h host_x -H $OBJ/known_hosts $OBJ/user_[bcde] || fail "add keys"
468$SSHADD -h host_a -h "host_a>host_b" -h "host_b>host_c" \
469	-h "host_c>host_d" -h "host_d>host_e" \
470	-H $OBJ/known_hosts -q $OBJ/user_a \
471	|| fatal "add key user_a multihop"
472prepare_multihop_expected filtered "a b a a c d e"
473hide_privatekeys
474$MULTIHOP_RUN "" a b a a c d e > $OBJ/ssh_output && \
475	fail "multihop cycle restricted deny succeded unexpectedly"
476restore_privatekeys
477
478verbose "multihop cycle restricted allow"
479clear_agent
480$SSHADD -q -h host_x -H $OBJ/known_hosts $OBJ/user_[bcde] || fail "add keys"
481$SSHADD -h host_a -h "host_a>host_b" -h "host_b>host_c" \
482	-h "host_c>host_d" -h "host_d>host_e" \
483	-h "host_b>host_a" -h "host_a>host_a" -h "host_a>host_c" \
484	-H $OBJ/known_hosts -q $OBJ/user_a \
485	|| fatal "add key user_a multihop"
486prepare_multihop_expected filtered "a b a a c d e"
487hide_privatekeys
488$MULTIHOP_RUN "" a b a a c d e > $OBJ/ssh_output || \
489	fail "multihop cycle restricted allow failed"
490diff $OBJ/ssh_output $OBJ/expect_a || fail "unexpected ssh output"
491restore_privatekeys
492
493