-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsu.sh
executable file
·281 lines (259 loc) · 9.21 KB
/
su.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
#!/bin/sh
# Note the shebang MUST be /bin/sh as this is what can be found in busybox,
# where there is no /usr/bin.
# This script will "descend" privileges to the user which name is present in the
# variable "$USER". It is injected in containers from dew in order to establish
# a minimal environment in the form of a HOME folder and an existing user.
set -eu
# Name of the docker group, when arranging for the user to be part of that
# group, so that it can access the UNIX domain socket also injected in the
# container.
DOCKER_GROUP=${DOCKER_GROUP:-docker}
# Path to the UNIX domain socket for communication with the Docker daemon on the
# host. Whenever relevant, the group id carried by the socket will also be given
# to the group being created.
DOCKER_SOCKET=${DOCKER_SOCKET:-/var/run/docker.sock}
# When set, this will print some extra logging around user/group creation and
# shell detection. The variable will be set when verbosity is set to trace.
DEW_DEBUG=${DEW_DEBUG:-0}
# Log the text passed as a paramter whenever DEW_DEBUG is 1
log() { [ "$DEW_DEBUG" = "1" ] && printf %s\\n "$1" >&2 || true; }
silent() {
if [ "$DEW_DEBUG" = "0" ]; then
"$@" > /dev/null
else
"$@"
fi
}
# Return the name of the Linux distribution group this is running on, in
# lowercase.
distro() {
if [ -r "/etc/os-release" ]; then
if grep -Eq '^ID_LIKE=' /etc/os-release; then
# shellcheck disable=SC1091 # /etc/os-release is standardised
lsb_dist=$(. /etc/os-release && printf %s\\n "$ID_LIKE" | awk '{print $1};')
else
# shellcheck disable=SC1091 # /etc/os-release is standardised
lsb_dist=$(. /etc/os-release && printf %s\\n "$ID")
fi
printf %s\\n "$lsb_dist" | tr '[:upper:]' '[:lower:]'
fi
}
# Create a user group called $USER with the identifier $DEW_GID
create_group() {
if [ -z "$(group_name "$2")" ]; then
log "Adding group $1 with gid $2"
case "$DEW_DISTRO" in
debian*)
silent addgroup -q --gid "$2" "$1";;
alpine*)
silent addgroup -g "$2" "$1";;
rhel | centos | fedora)
silent groupadd -g "$2" "$1";;
*)
# Go old style, just add an entry to the /etc/group file
printf "%s:x:%d:\n" "$1" "$2" >> /etc/group;;
esac
fi
group_name "$2"
}
# Make user $1 member of group $2
group_member() {
if grep -qE "^${2}:" /etc/group; then
case "$DEW_DISTRO" in
debian*)
silent adduser -q "$1" "$2";;
alpine*)
silent addgroup "$1" "$2";;
rhel | centos | fedora)
silent groupmems -a "$1" -g "$2";;
*)
# Go old style, just modify the /etc/group file
gid=$(grep -E "^${2}:" /etc/group|cut -d: -f3)
if grep -q "^${2}:" /etc/group | grep -E ':$'; then
sed -i -e "s/${2}:x:${gid}:/${2}:x:${gid}:${1}/" /etc/group
else
sed -iE -e "s/${2}:x:${gid}:(.*)/${2}:x:${gid}:\1,${1}/" /etc/group
fi
;;
esac
else
log "Group $2 does not exist!"
fi
}
db_get() {
if cmd_exists "getent"; then
getent "$1" "$2"
else
if printf %s\\n "$2" | grep -qE '^[0-9]+$'; then
field=3
else
field=1
fi
while IFS= read -r line; do
if [ "$(printf %s\\n "$line" | cut -d: -f"$field")" = "$2" ]; then
printf %s\\n "$line"
return
fi
done < "/etc/$1"
fi
}
group_name() { db_get group "$1" | cut -d: -f1; }
user_name() { db_get passwd "$1" | cut -d: -f1; }
# Create a user $USER, belonging to the group of the same name (created by
# function above), with identifier $DEW_UID and shell $SHELL, as detected at the
# when this script starts
create_user() {
if [ -z "$(user_name "$DEW_UID")" ]; then
log "Adding user $USER with id $DEW_UID to /etc/passwd. Shell: $SHELL"
case "$DEW_DISTRO" in
debian*)
silent adduser \
-q \
--home "$HOME" \
--shell "$SHELL" \
--gid "$DEW_GID" \
--disabled-password \
--uid "$DEW_UID" \
--gecos "" \
"$USER";;
alpine*)
# Alpine uses the name of the group, which is the same as the user in our
# case.
silent adduser \
-h "$HOME" \
-s "$SHELL" \
-G "$(group_name "$DEW_GID")" \
-D \
-u "$DEW_UID" \
-g "" \
"$USER";;
rhel | centos | fedora)
silent useradd \
--home-dir "$HOME" \
--shell "$SHELL" \
--gid "$DEW_GID" \
--password "" \
--uid "$DEW_UID" \
--no-create-home \
--comment "" \
"$USER";;
*)
# Go old style, just add an entry to the /etc/passwd file
printf "%s:x:%d:%d::%s:%s\\n" \
"$USER" \
"$DEW_UID" \
"$DEW_GID" \
"$HOME" \
"$SHELL" >> /etc/passwd;;
esac
fi
user_name "$DEW_UID"
}
cmd_exists() { command -v "$1" >/dev/null 2>&1; }
# Decide upon a good plausible shell to run, the order might be questionable,
# but this covers a large set of distributions and shells.
SHELL=
for s in bash ash sh; do
if cmd_exists "$s"; then
SHELL=$(command -v "$s")
break
fi
done
log "Default shell detected as $SHELL"
if [ "${USER:-}" = "root" ] || [ -z "${USER:-}" ]; then
# When running as root or an unknown user, we just replace ourselves with
# either the default shell that we guessed existed in the container, or with
# the one forced in from the outside by dew.
if [ -n "${DEW_SHELL:-}" ]; then
log "Replacing ourselves with $DEW_SHELL $*"
exec "$DEW_SHELL" "$@"
else
log "Replacing ourselves with $SHELL $*"
exec "$SHELL" "$@"
fi
else
# Otherwise (and ... in most cases), arrange for a minimal environment to
# exist in the container before becoming the requested user and elevating down
# to lesser privileges.
# Discover distro, will be used in functions
DEW_DISTRO=$(distro)
# Create a home for the user and make sure it is accessible for RW
if [ -n "$HOME" ] && ! [ -d "$HOME" ]; then
log "Creating home directory $HOME"
mkdir -p "$HOME"
fi
if [ -n "$HOME" ] && [ -d "$HOME" ]; then
log "Changing owner of $HOME to ${DEW_UID:-0}:${DEW_GID:-0}"
chown "${DEW_UID:-0}:${DEW_GID:-0}" "${HOME}"
chmod ug+rwx "$HOME"
fi
# If a group identifier was specified, arrange for the group to exist. The
# group will be named after the user. Once done, create a user inside that
# group.
if [ -f "/etc/group" ] && [ -n "${DEW_GID:-}" ]; then
silent create_group "$USER" "$DEW_GID"
# Create the user if it does not already exist. Arrange for the default
# shell to be the one discovered at the beginning of this script. If a user
# with that UID already exists, switch the username to the one already
# registered for the UID, as nothing else would work.
if [ -f "/etc/passwd" ] && [ -n "${DEW_UID:-}" ]; then
CUSER=$(create_user)
if [ "$CUSER" != "$USER" ]; then
CHOME=$(db_get passwd "$CUSER" | cut -d: -f6)
log "Image user $CUSER different from caller, linking $CHOME to $HOME"
rm -rf "$CHOME"
ln -sf "$HOME" "$CHOME"
USER=$CUSER
fi
fi
fi
# Arrange for the user to be part of the Docker group by creating the group
# and letting the user to be a member of the group. We are able to cope with
# the fact that there might already be a group with the same id within the
# container, in which case, we just reuse its name so we can map onto the
# host's group.
if [ -f "/etc/group" ] && \
! cut -d: -f1 /etc/group | grep -q "^${DOCKER_GROUP}:" && \
[ -S "$DOCKER_SOCKET" ]; then
dgid=$(stat -c '%g' "$DOCKER_SOCKET")
log "Making user $USER member of the group $DOCKER_GROUP with id $dgid to /etc/passwd"
silent create_group "$DOCKER_GROUP" "$dgid" || true
silent group_member "$USER" "$(group_name "$dgid")" || true
fi
# Now run an interactive shell with lesser privileges, i.e. as the user that
# we have just created. This will either be the default shell, or the one
# specified from the outside by dew. In theory, there is a missing "else"
# branch to the if-statement, but even busybox has an implementation of su!
# This script would however fail in bare environments, i.e. raw images with a
# single binary in them.
if command -v "sudo" >/dev/null 2>&1; then
if [ -z "${DEW_SHELL:-}" ]; then
log "Becoming $USER, running $* with sudo"
exec sudo -u "$USER" -- "$@"
else
log "Becoming $USER, running $DEW_SHELL $* (at: $(command -v "$DEW_SHELL")) with sudo"
exec sudo -u "$USER" -- "$(command -v "$DEW_SHELL")" "$@"
fi
elif command -v "su" >/dev/null 2>&1; then
# Create a temporary script that will call the remaining of the arguments,
# with the DEW_SHELL prefixed if relevant. This is because su is evil and -c
# option only takes a single command...
tmpf=$(mktemp)
printf '#!%s\n' "$SHELL" > "$tmpf"
printf "exec" >> "$tmpf"
if [ -n "${DEW_SHELL:-}" ]; then
printf ' "%s"' "$DEW_SHELL" >> "$tmpf"
fi
for a in "$@"; do
[ -n "$a" ] && printf ' "%s"' "$a" >> "$tmpf"
done
printf \\n >> "$tmpf"
chmod a+rx "$tmpf"
log "Becoming $USER, running ${DEW_SHELL:-} $* with su"
exec su -c "$tmpf" "$USER"
else
log "Can neither find su, nor sudo"
exit 1
fi
fi