Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 26 additions & 8 deletions internal/recipes/bundled/python-dev/install-alpine.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,34 @@ fi
stoat_pkg_setup
stoat_pkg_install python3 py3-pip

# busybox su recognises options anywhere, so a -p among the trailing arguments
# becomes su's own preserve-environment flag and never reaches the command.
# Quote the argument list into -c instead of passing it positionally.
su_user() {
quoted=
for arg do
quoted="$quoted'$(printf '%s' "$arg" | sed "s/'/'\\\\''/g")' "
done
su -s /bin/sh "$user" -c "exec $quoted"
}

# Alpine's sudoers file grants root nothing, so sudo is on PATH and refuses
# every command. Probe each tool once and keep the first that runs.
as_user() {
if command -v runuser >/dev/null 2>&1; then
runuser -u "$user" -- "$@"
elif command -v sudo >/dev/null 2>&1; then
sudo -n -u "$user" -- "$@"
else
command=$1
shift
su -s /bin/sh "$user" -c 'exec "$@"' stoat "$command" "$@"
if [ -z "${as_user_tool:-}" ]; then
if command -v runuser >/dev/null 2>&1 && runuser -u "$user" -- true 2>/dev/null; then
as_user_tool=runuser
elif command -v sudo >/dev/null 2>&1 && sudo -n -u "$user" -- true 2>/dev/null; then
as_user_tool=sudo
else
as_user_tool=su
fi
fi
case "$as_user_tool" in
runuser) runuser -u "$user" -- "$@" ;;
sudo) sudo -n -u "$user" -- "$@" ;;
*) su_user "$@" ;;
esac
}

python_bin=$(command -v python3 2>/dev/null || command -v python 2>/dev/null || true)
Expand Down
34 changes: 26 additions & 8 deletions internal/recipes/bundled/python-dev/install-arch.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,34 @@ fi
stoat_pkg_setup
stoat_pkg_install python python-pip

# busybox su recognises options anywhere, so a -p among the trailing arguments
# becomes su's own preserve-environment flag and never reaches the command.
# Quote the argument list into -c instead of passing it positionally.
su_user() {
quoted=
for arg do
quoted="$quoted'$(printf '%s' "$arg" | sed "s/'/'\\\\''/g")' "
done
su -s /bin/sh "$user" -c "exec $quoted"
}

# A sudo binary on PATH is not a working sudo: Alpine's sudoers grants root
# nothing. Probe each tool once and keep the first that runs.
as_user() {
if command -v runuser >/dev/null 2>&1; then
runuser -u "$user" -- "$@"
elif command -v sudo >/dev/null 2>&1; then
sudo -n -u "$user" -- "$@"
else
command=$1
shift
su -s /bin/sh "$user" -c 'exec "$@"' stoat "$command" "$@"
if [ -z "${as_user_tool:-}" ]; then
if command -v runuser >/dev/null 2>&1 && runuser -u "$user" -- true 2>/dev/null; then
as_user_tool=runuser
elif command -v sudo >/dev/null 2>&1 && sudo -n -u "$user" -- true 2>/dev/null; then
as_user_tool=sudo
else
as_user_tool=su
fi
fi
case "$as_user_tool" in
runuser) runuser -u "$user" -- "$@" ;;
sudo) sudo -n -u "$user" -- "$@" ;;
*) su_user "$@" ;;
esac
}

python_bin=$(command -v python3 2>/dev/null || command -v python 2>/dev/null || true)
Expand Down
34 changes: 26 additions & 8 deletions internal/recipes/bundled/python-dev/install-debian.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,34 @@ fi
stoat_pkg_setup
stoat_pkg_install python3 python3-pip python3-venv

# busybox su recognises options anywhere, so a -p among the trailing arguments
# becomes su's own preserve-environment flag and never reaches the command.
# Quote the argument list into -c instead of passing it positionally.
su_user() {
quoted=
for arg do
quoted="$quoted'$(printf '%s' "$arg" | sed "s/'/'\\\\''/g")' "
done
su -s /bin/sh "$user" -c "exec $quoted"
}

# A sudo binary on PATH is not a working sudo: Alpine's sudoers grants root
# nothing. Probe each tool once and keep the first that runs.
as_user() {
if command -v runuser >/dev/null 2>&1; then
runuser -u "$user" -- "$@"
elif command -v sudo >/dev/null 2>&1; then
sudo -n -u "$user" -- "$@"
else
command=$1
shift
su -s /bin/sh "$user" -c 'exec "$@"' stoat "$command" "$@"
if [ -z "${as_user_tool:-}" ]; then
if command -v runuser >/dev/null 2>&1 && runuser -u "$user" -- true 2>/dev/null; then
as_user_tool=runuser
elif command -v sudo >/dev/null 2>&1 && sudo -n -u "$user" -- true 2>/dev/null; then
as_user_tool=sudo
else
as_user_tool=su
fi
fi
case "$as_user_tool" in
runuser) runuser -u "$user" -- "$@" ;;
sudo) sudo -n -u "$user" -- "$@" ;;
*) su_user "$@" ;;
esac
}

python_bin=$(command -v python3 2>/dev/null || command -v python 2>/dev/null || true)
Expand Down
34 changes: 26 additions & 8 deletions internal/recipes/bundled/python-dev/install-rpm.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,34 @@ fi
stoat_pkg_setup
stoat_pkg_install python3 python3-pip

# busybox su recognises options anywhere, so a -p among the trailing arguments
# becomes su's own preserve-environment flag and never reaches the command.
# Quote the argument list into -c instead of passing it positionally.
su_user() {
quoted=
for arg do
quoted="$quoted'$(printf '%s' "$arg" | sed "s/'/'\\\\''/g")' "
done
su -s /bin/sh "$user" -c "exec $quoted"
}

# A sudo binary on PATH is not a working sudo: Alpine's sudoers grants root
# nothing. Probe each tool once and keep the first that runs.
as_user() {
if command -v runuser >/dev/null 2>&1; then
runuser -u "$user" -- "$@"
elif command -v sudo >/dev/null 2>&1; then
sudo -n -u "$user" -- "$@"
else
command=$1
shift
su -s /bin/sh "$user" -c 'exec "$@"' stoat "$command" "$@"
if [ -z "${as_user_tool:-}" ]; then
if command -v runuser >/dev/null 2>&1 && runuser -u "$user" -- true 2>/dev/null; then
as_user_tool=runuser
elif command -v sudo >/dev/null 2>&1 && sudo -n -u "$user" -- true 2>/dev/null; then
as_user_tool=sudo
else
as_user_tool=su
fi
fi
case "$as_user_tool" in
runuser) runuser -u "$user" -- "$@" ;;
sudo) sudo -n -u "$user" -- "$@" ;;
*) su_user "$@" ;;
esac
}

python_bin=$(command -v python3 2>/dev/null || command -v python 2>/dev/null || true)
Expand Down
34 changes: 32 additions & 2 deletions internal/recipes/samples_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,28 @@ func TestBundledPythonDevCreatesAndPreservesVenv(t *testing.T) {
}
}

// Alpine ships a sudoers file that grants root nothing, so sudo is on PATH and
// still refuses every command. The recipe must fall through to su instead of
// failing the runcmd and leaving cloud-init in error.
func TestBundledPythonDevFallsBackWhenEscalationRefuses(t *testing.T) {
t.Setenv("STOAT_FAKE_NO_ESCALATION", "1")
account := currentTestAccount(t)
root := t.TempDir()

for _, guestOS := range []string{"alpine", "debian", "arch", "fedora"} {
body := bundledScript(t, "python-dev", guestOS)
venvDir := filepath.Join(root, guestOS, "env")
output, err := runBundledPython(t, body, account.Username, venvDir,
filepath.Join(root, guestOS+"-output"), filepath.Join(root, guestOS+"-calls"))
if err != nil {
t.Fatalf("%s: %v\n%s", guestOS, err, output)
}
if _, err := os.Stat(filepath.Join(venvDir, "pyvenv.cfg")); err != nil {
t.Errorf("%s: environment was not created: %v", guestOS, err)
}
}
}

func TestBundledPythonDevRefusesInvalidTargets(t *testing.T) {
body := bundledScript(t, "python-dev", "alpine")
account := currentTestAccount(t)
Expand Down Expand Up @@ -449,6 +471,10 @@ func writeHermeticCommandFakes(t *testing.T, bin string) {
t.Helper()
accountSwitch := `#!/bin/sh
set -eu
if [ -n "${STOAT_FAKE_NO_ESCALATION:-}" ]; then
echo "root is not in the sudoers file." >&2
exit 1
fi
while [ "$#" -gt 0 ]; do
case "$1" in
-u|-g|-s) shift 2;;
Expand All @@ -464,16 +490,20 @@ exec "$@"
t.Fatal(err)
}
}
// busybox su, which is what Alpine has, recognises options anywhere on the
// command line. A -p among the trailing arguments becomes su's own
// preserve-environment flag and never reaches the command, so this fake
// drops every dash argument and passes nothing positional to the shell.
if err := os.WriteFile(filepath.Join(bin, "su"), []byte(`#!/bin/sh
set -eu
user=
command=
while [ "$#" -gt 0 ]; do
case "$1" in
-c) command=$2; shift 2;;
-s) shift 2;;
-*) shift;;
-c) command=$2; shift 2; break;;
*) user=$1; shift;;
*) [ -n "$user" ] || user=$1; shift;;
esac
done
[ -n "$command" ]
Expand Down
Loading