Skip to content

Commit e826d4c

Browse files
committed
Initial commit
0 parents  commit e826d4c

File tree

5 files changed

+319
-0
lines changed

5 files changed

+319
-0
lines changed

LICENSE

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
Permission is hereby granted, free of charge, to any person obtaining
2+
a copy of this software and associated documentation files (the
3+
"Software"), to deal in the Software without restriction, including
4+
without limitation the rights to use, copy, modify, merge, publish,
5+
distribute, sublicense, and/or sell copies of the Software, and to
6+
permit persons to whom the Software is furnished to do so, subject to
7+
the following conditions:
8+
9+
The above copyright notice and this permission notice shall be included
10+
in all copies or substantial portions of the Software.
11+
12+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
13+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
14+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
15+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
16+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
17+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
18+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Makefile

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
PREFIX=/usr
2+
3+
VERSION = 0.1
4+
DATE = 2015-10-02
5+
6+
all:
7+
8+
clean:
9+
rm -rf restricted-ssh-commands.1 test
10+
11+
test/restricted-ssh-commands: restricted-ssh-commands
12+
mkdir -p test
13+
sed 's@config_file="/etc@config_file="$${TEST_ROOT-}/etc@' $^ > $@
14+
chmod +x $@
15+
16+
check: test/restricted-ssh-commands
17+
./test-restricted-ssh-commands
18+
19+
%.1: %.pod
20+
pod2man --center=" " --release="restricted-ssh-commands" -d "$(DATE)" $^ $@
21+
22+
doc: restricted-ssh-commands.1
23+
24+
install: doc
25+
install -D -m 755 restricted-ssh-commands $(DESTDIR)$(PREFIX)/lib/restricted-ssh-commands
26+
install -D -m 644 restricted-ssh-commands.1 $(DESTDIR)$(PREFIX)/share/man/man1/restricted-ssh-commands.1
27+
28+
%.tar.xz: LICENSE Makefile restricted-ssh-commands restricted-ssh-commands.pod test-restricted-ssh-commands
29+
tar -cJf $@ --transform 's,^,restricted-ssh-commands-$(VERSION)/,' $^
30+
31+
dist: restricted-ssh-commands-$(VERSION).tar.xz
32+
33+
.PHONY: all clean check doc dist install

restricted-ssh-commands

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
#!/bin/bash
2+
set -eu
3+
4+
# Copyright (C) 2015, Benjamin Drung <benjamin.drung@profitbricks.com>
5+
#
6+
# Permission is hereby granted, free of charge, to any person obtaining
7+
# a copy of this software and associated documentation files (the
8+
# "Software"), to deal in the Software without restriction, including
9+
# without limitation the rights to use, copy, modify, merge, publish,
10+
# distribute, sublicense, and/or sell copies of the Software, and to
11+
# permit persons to whom the Software is furnished to do so, subject to
12+
# the following conditions:
13+
#
14+
# The above copyright notice and this permission notice shall be included
15+
# in all copies or substantial portions of the Software.
16+
#
17+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
20+
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
21+
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
22+
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
23+
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24+
25+
# Note: Matching the regular expressions requires bash
26+
27+
config="${1-}"
28+
if test -z "$config"; then
29+
if test -n "${USER-}"; then
30+
config="$USER"
31+
else
32+
config=$(id -u -n)
33+
fi
34+
fi
35+
36+
set ${SSH_ORIGINAL_COMMAND-}
37+
38+
config_file="/etc/restricted-ssh-commands/$config"
39+
if test ! -f "$config_file"; then
40+
msg="No configuration in $config_file. All commands are denied."
41+
echo "${0##*/}: $msg" >&2
42+
logger -t ${0##*/} -- "$msg"
43+
exit 125
44+
fi
45+
46+
num_rules=0
47+
while IFS='' read -r line; do
48+
# Skip empty lines and lines starting with hashes
49+
if ! [[ "$line" =~ ^[[:space:]]*(#|$) ]]; then
50+
if [[ "$@" =~ $line ]]; then
51+
if test -n "${RSC_VERBOSE-}"; then
52+
echo "${0##*/}: \"$@\" matches \"$line\"" >&2
53+
fi
54+
exec "$@"
55+
else
56+
((num_rules+=1))
57+
if test -n "${RSC_VERBOSE-}"; then
58+
echo "${0##*/}: Regular expression does not match: \"$line\"" >&2
59+
fi
60+
fi
61+
elif test -n "${RSC_VERBOSE-}"; then
62+
echo "${0##*/}: Skipping commented/empty configuration line: \"$line\"" >&2
63+
fi
64+
done < "$config_file"
65+
66+
if test "$num_rules" -eq 0; then
67+
msg="Empty configuration in $config_file. All commands are denied."
68+
echo "${0##*/}: $msg" >&2
69+
logger -t ${0##*/} -- "$msg"
70+
exit 125
71+
else
72+
if test "$num_rules" -eq 1; then
73+
msg="the one allow rule"
74+
else
75+
msg="any of the $num_rules allow rules"
76+
fi
77+
msg="Rejecting command \"$@\". It does not match $msg in $config_file."
78+
echo "${0##*/}: $msg" >&2
79+
logger -t ${0##*/} -- "$msg"
80+
exit 124
81+
fi

restricted-ssh-commands.pod

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
=encoding utf8
2+
3+
=head1 NAME
4+
5+
restricted-ssh-commands - Restrict SSH users to a predefined set of commands
6+
7+
=head1 SYNOPSIS
8+
9+
B</usr/lib/restricted-ssh-commands> [I<config>]
10+
11+
=head1 DESCRIPTION
12+
13+
restricted-ssh-commands is intended to be called by SSH to restrict a
14+
user to only run specific commands. A list of allowed regular
15+
expressions can be configured in F</etc/restricted-ssh-commands/>. The
16+
requested command has to match at least one regular expression.
17+
Otherwise it will be rejected.
18+
19+
restricted-ssh-commands is useful to grant restricted access via SSH to
20+
do only certain task. For example, it could allow a user to upload a Debian
21+
packages via scp and run reprepro processincoming.
22+
23+
The optional I<config> parameter is the name of the configuration inside
24+
F</etc/restricted-ssh-commands/> that should be used. If I<config> is omitted,
25+
the user name will be used.
26+
27+
=head1 USAGE
28+
29+
Create a configuration file in F</etc/restricted-ssh-commands/$config> and add
30+
following line to F<~/.ssh/authorized_keys> to use it
31+
32+
command="/usr/lib/restricted-ssh-commands",no-port-forwarding,\
33+
no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa [...]
34+
35+
=head1 EXIT STATUS
36+
37+
B<restricted-ssh-commands> will exit with the exit status from the called
38+
command if the command is allowed and therefore executed. If the command
39+
is rejected, B<restricted-ssh-commands> will exit with one of the following
40+
exit codes.
41+
42+
=over 8
43+
44+
=item C<124>
45+
46+
A configuration file was found and contains at least one regular expression, but
47+
the requested command does not match any of those regular expressions.
48+
49+
=item C<125>
50+
51+
The configuration file is missing or does not contain any regular expressions.
52+
Thus all commands are rejected.
53+
54+
=back
55+
56+
=head1 EXAMPLES
57+
58+
Imagine you have a Debian package repository on a host using reprepro and
59+
you want to allow package upload to it. Assuming the user is reprepro and the
60+
package configuration is stored in F</srv/reprepro>, you would create the
61+
configuration file F</etc/restricted-ssh-commands/reprepro> containing these
62+
three regular expressions:
63+
64+
^scp -p( -d)? -t( --)? /srv/reprepro/incoming(/[^ /]*)?$
65+
^chmod 0644 /srv/reprepro/incoming/[^ /]*$
66+
^reprepro ( -V)? -b /srv/reprepro processincoming foobar$
67+
68+
=head1 FILES
69+
70+
The configuration files are placed in F</etc/restricted-ssh-commands/>. Each
71+
line in the configuration file represents one extended regular expression.
72+
Lines starting with # are considered as comments and are ignored. Empty lines
73+
(containing only whitespaces) are ignored, too.
74+
75+
=head1 SEE ALSO
76+
77+
Regular expressions on
78+
http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_04_01.html
79+
80+
Section 9.4 Extended Regular Expressions (ERE) on
81+
http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap09.html
82+
83+
=head1 AUTHOR
84+
85+
B<restricted-ssh-commands> and this manpage have been written by Benjamin Drung
86+
<benjamin.drung@profitbricks.com>.

test-restricted-ssh-commands

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
#!/bin/sh
2+
3+
# Copyright (C) 2015, Benjamin Drung <benjamin.drung@profitbricks.com>
4+
#
5+
# Permission is hereby granted, free of charge, to any person obtaining
6+
# a copy of this software and associated documentation files (the
7+
# "Software"), to deal in the Software without restriction, including
8+
# without limitation the rights to use, copy, modify, merge, publish,
9+
# distribute, sublicense, and/or sell copies of the Software, and to
10+
# permit persons to whom the Software is furnished to do so, subject to
11+
# the following conditions:
12+
#
13+
# The above copyright notice and this permission notice shall be included
14+
# in all copies or substantial portions of the Software.
15+
#
16+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19+
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20+
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21+
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22+
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23+
24+
COMMAND="${0%/*}/test/restricted-ssh-commands"
25+
26+
runCommand() {
27+
local ssh_param="$1"
28+
local param="$2"
29+
local exp_stdout="$3"
30+
local exp_stderr="$4"
31+
local exp_retval=$5
32+
local stdoutF="${SHUNIT_TMPDIR}/stdout"
33+
local stderrF="${SHUNIT_TMPDIR}/stderr"
34+
eval TEST_ROOT=${SHUNIT_TMPDIR} SSH_ORIGINAL_COMMAND="${ssh_param}" "${COMMAND} $param" > ${stdoutF} 2> ${stderrF}
35+
retval=$?
36+
assertEquals "standard output of ${COMMAND} $param\n" "$exp_stdout" "$(cat ${stdoutF})"
37+
assertEquals "error output of ${COMMAND} $param\n" "$exp_stderr" "$(cat ${stderrF})"
38+
assertEquals "return value of ${COMMAND} $param\n" $exp_retval $retval
39+
}
40+
41+
add_rule() {
42+
local config_name="$1"
43+
local config_content="$2"
44+
mkdir -p "${SHUNIT_TMPDIR}/etc/restricted-ssh-commands"
45+
printf "%s\n" "$config_content" >> "${SHUNIT_TMPDIR}/etc/restricted-ssh-commands/${config_name}"
46+
}
47+
48+
success() {
49+
runCommand "$1" "$2" "$3" "" ${4-0}
50+
}
51+
52+
failure() {
53+
runCommand "$1" "$2" "" "$3" ${4-124}
54+
}
55+
56+
tearDown() {
57+
rm -rf ${SHUNIT_TMPDIR}/etc
58+
}
59+
60+
test_missing_config() {
61+
failure "true" "foo" "restricted-ssh-commands: No configuration in ${SHUNIT_TMPDIR}/etc/restricted-ssh-commands/foo. All commands are denied." 125
62+
}
63+
64+
test_empty_config() {
65+
add_rule "foo" ""
66+
failure "true" "foo" "restricted-ssh-commands: Empty configuration in ${SHUNIT_TMPDIR}/etc/restricted-ssh-commands/foo. All commands are denied." 125
67+
}
68+
69+
test_single_rule_config() {
70+
add_rule "$(id -un)" "^echo"
71+
failure "true" "" "restricted-ssh-commands: Rejecting command \"true\". It does not match the one allow rule in ${SHUNIT_TMPDIR}/etc/restricted-ssh-commands/$(id -un)."
72+
}
73+
74+
test_two_rules_config() {
75+
add_rule "bar" "^echo"
76+
add_rule "bar" "^false"
77+
failure "true" "bar" "restricted-ssh-commands: Rejecting command \"true\". It does not match any of the 2 allow rules in ${SHUNIT_TMPDIR}/etc/restricted-ssh-commands/bar."
78+
}
79+
80+
test_matching_rule() {
81+
add_rule "foo" '^true$'
82+
success "true" "foo" ""
83+
}
84+
85+
test_user_config() {
86+
add_rule "$(id -un)" '^true$'
87+
success "true" "" ""
88+
}
89+
90+
test_custom_config() {
91+
add_rule "foobar" '^true$'
92+
success "true" "foobar" ""
93+
}
94+
95+
test_failing_command() {
96+
add_rule "bla" '^true$'
97+
add_rule "bla" '^false$'
98+
failure "false" "bla" "" 1
99+
}
100+
101+
. shunit2

0 commit comments

Comments
 (0)