diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 44720039c..4d9642710 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -219,6 +219,19 @@ jobs: name: coverage.${{ matrix.index }} path: test-results/coverage + sudo-preserve-env: + name: sudo-preserve-env + runs-on: ubuntu-26.04 + needs: check-commit + if: ${{ needs.check-commit.outputs.skip != 'true' }} + timeout-minutes: 10 + + steps: + - uses: actions/checkout@v7 + + - name: check that the preserved environment survives sudo-rs + run: ./tests/ci/sudo_preserve_env.sh + publish-test-results: name: publish-test-results needs: unit-tests diff --git a/Makefile b/Makefile index 6fafe4b3c..bab57eee7 100644 --- a/Makefile +++ b/Makefile @@ -126,10 +126,10 @@ else endif plugin-dependencies: plugn procfile-util - sudo -E dokku plugin:install-dependencies --core + sudo dokku plugin:install-dependencies --core plugins: plugn procfile-util docker - sudo -E dokku plugin:install --core + sudo dokku plugin:install --core dependencies: apt-update jq docker-image-labeler docker-container-healthchecker lambda-builder netrc sshcommand plugn procfile-util docker help2man man-db sigil dos2unix parallel $(MAKE) -e stack diff --git a/bootstrap.sh b/bootstrap.sh index 714c07ebb..eb923d379 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -110,14 +110,14 @@ install-dokku() { elif [[ "$use_plugin" == "true" ]]; then install-dokku-from-package "$DOKKU_SEMVER" echo "--> Running post-install dependency installation" - sudo -E dokku plugin:install-dependencies --core + sudo dokku plugin:install-dependencies --core else install-dokku-from-source "$DOKKU_TAG" fi else install-dokku-from-package echo "--> Running post-install dependency installation" - sudo -E dokku plugin:install-dependencies --core + sudo dokku plugin:install-dependencies --core fi } diff --git a/docs/configuration/environment-variables.md b/docs/configuration/environment-variables.md index afbc7861c..968c63308 100644 --- a/docs/configuration/environment-variables.md +++ b/docs/configuration/environment-variables.md @@ -138,6 +138,9 @@ The following config variables have special meanings and can be set in a variety | `DOKKU_EVENTS_LOGFILE` | `$DOKKU_LOGS_DIR/events.log` | `/etc/environment`
`~dokku/.dokkurc`
`~dokku/.dokkurc/*` | Where the events log file is written to. | | `DOKKU_APP_NAME` | none | `--app APP` flag | Name of application to work on. Respected by core plugins. | | `DOKKU_APPS_FORCE_DELETE` | none | `--force` flag | Whether to force delete an application. Also used by other plugins for destructive actions. | +| `DOKKU_DISABLE_TTY` | none | `dokku run --no-tty`
environment | Forces `dokku run` to treat the session as non-interactive. | +| `DOKKU_FORCE_TTY` | none | `dokku run --force-tty`
environment | Forces `dokku run` to treat the session as interactive. | +| `DOKKU_PRESERVE_ENV` | none | `/etc/default/dokku`
`~dokku/.dokkurc`
`~dokku/.dokkurc/*`
environment | Comma-separated list of extra environment variable names to carry across the privilege drop to the `dokku` user. See [Forwarding environment variables](#forwarding-environment-variables). | | `DOKKU_CHECKS_URL` | `https://dokku.com/docs/deployment/zero-downtime-deploys/` | `/etc/environment`
`~dokku/.dokkurc`
`~dokku/.dokkurc/*` | Url displayed during deployment when no CHECKS file exists. | | `DOKKU_QUIET_OUTPUT` | none | `--quiet` flag | Silences certain header output for `dokku` commands. | | `DOKKU_RM_CONTAINER` | none | `dokku config:set`
| Deprecated: Whether to keep `dokku run` containers around or not. | @@ -145,6 +148,45 @@ The following config variables have special meanings and can be set in a variety | `DOKKU_SYSTEM_GROUP` | `dokku` | `/etc/environment`
`~dokku/.dokkurc`
`~dokku/.dokkurc/*` | System group to chown files as. | | `DOKKU_SYSTEM_USER` | `dokku` | `/etc/environment`
`~dokku/.dokkurc`
`~dokku/.dokkurc/*` | System user to chown files as. | +## Forwarding environment variables + +When `dokku` is invoked by any user other than `dokku` - `root`, or a member of the `sudo` group - it +re-executes itself as the `dokku` user. Most configuration does not need to survive that step, since +`/etc/default/dokku`, `~dokku/dokkurc` and `~dokku/.dokkurc/*` are read again by the re-executed +process, and `/etc/environment` is applied by PAM. + +What cannot be recomputed is state that belongs to the invocation itself, so those variables are +named explicitly and forwarded: + +| Variable | Source | +| --- | --- | +| `DOKKU_APP_NAME`, `DOKKU_APPS_FORCE_DELETE`, `DOKKU_QUIET_OUTPUT`, `DOKKU_TRACE` | the `--app`, `--force`, `--quiet` and `--trace` global flags, or the environment | +| `DOKKU_GLOBAL_FLAGS` | every global flag as given, replayed by plugins that need them | +| `DOKKU_DISABLE_TTY`, `DOKKU_FORCE_TTY` | the `dokku run` tty flags, or the environment | +| `DOKKU_PRESERVE_ENV` | the list below | +| `SSH_USER`, `SSH_NAME`, `NAME`, `FINGERPRINT`, `SSH_ORIGINAL_COMMAND` | the ssh session the command arrived on | +| `DOCKER_BIN`, `DOKKU_CNB_BUILDER`, `DOKKU_EVENTS_LOGFILE`, `DOKKU_HOST_ROOT`, `DOKKU_IMAGE`, `DOKKU_LIB_PATH`, `DOKKU_LOGS_DIR`, `DOKKU_LOGS_HOST_DIR`, `DOKKU_ROOT`, `DOKKU_SYSTEM_GROUP`, `DOKKU_SYSTEM_USER`, `PLUGIN_PATH`, `PLUGIN_AVAILABLE_PATH`, `PLUGIN_ENABLED_PATH`, `PLUGIN_CORE_PATH`, `PLUGIN_CORE_AVAILABLE_PATH`, `PLUGIN_CORE_ENABLED_PATH` | the environment, so the re-executed process derives the same paths as the caller | +| `DOKKU_INIT_SYSTEM`, `DOKKU_LIB_HOST_ROOT` | the environment, which is where they live while a Docker image is being built | + +`DOKKU_PID` is deliberately not forwarded: it is unset before the privilege drop so the re-executed +process gets its own. + +To forward anything else - a variable read by a third-party plugin, or a one-off override of a +setting that normally lives in `/etc/default/dokku` - name it in `DOKKU_PRESERVE_ENV`: + +```shell +DOKKU_PRESERVE_ENV=DOCKER_BUILDKIT DOCKER_BUILDKIT=0 dokku ps:rebuild node-js-app +``` + +Set it persistently to forward a variable for every invocation: + +```shell +echo "export DOKKU_PRESERVE_ENV=MY_PLUGIN_DEBUG" | sudo tee -a /etc/default/dokku +``` + +Variables sudo manages for the target user - `HOME`, `PATH`, `SHELL`, `USER`, `LOGNAME`, `MAIL` and +`SUDO_*` - are never forwarded, and naming them in `DOKKU_PRESERVE_ENV` is not supported. + ## Properties ### Internal properties diff --git a/docs/deployment/user-management.md b/docs/deployment/user-management.md index 27922f67c..f9597dac3 100644 --- a/docs/deployment/user-management.md +++ b/docs/deployment/user-management.md @@ -135,3 +135,9 @@ Use `visudo /etc/sudoers.d/dokku-users`, or `visudo /etc/sudoers` to add the fol ``` %dokku ALL=(ALL:ALL) NOPASSWD:SETENV: /usr/bin/dokku ``` + +The `SETENV:` tag is required. When a user other than `dokku` runs the `dokku` binary, it re-executes +itself as the `dokku` user and names the environment variables that have to survive that step via +`sudo --preserve-env=`. Without `SETENV:` those variables are filtered out, and global flags such as +`--quiet`, `--trace` and `--force` stop taking effect. See +[Forwarding environment variables](/docs/configuration/environment-variables.md#forwarding-environment-variables). diff --git a/dokku b/dokku index 495e7aa9f..4c3609258 100755 --- a/dokku +++ b/dokku @@ -67,6 +67,9 @@ export DOKKU_GLOBAL_RUN_ARGS="--label=org.label-schema.schema-version=1.0 --labe source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +# the original argv is kept so the re-exec as the dokku user can replay it verbatim +# and let the child re-parse the global flags for itself. parse_args and the +# flag-stripping loop below must therefore stay pure functions of argv. parse_args "$@" args=("$@") skip_arg=false @@ -91,7 +94,9 @@ if [[ $(id -un) != "dokku" ]]; then if [[ ! $1 =~ plugin:* ]] && [[ $1 != "ssh-keys:add" ]] && [[ $1 != "ssh-keys:remove" ]] && [[ $1 != "scheduler-k3s:initialize" ]] && [[ $1 != "scheduler-k3s:uninstall" ]]; then unset DOKKU_PID TMP TMPDIR TEMP TEMPDIR export SSH_USER=$(id -un) - sudo -u dokku -E -H "$0" "$@" + read -ra preserved_env_names <<<"$(fn-cli-preserved-env-names)" + preserve_env_flag="$(fn-sudo-preserve-env-flag "${preserved_env_names[@]}")" + sudo -u dokku "$preserve_env_flag" -H "$0" "${args[@]}" exit $? fi fi diff --git a/plugins/common/functions b/plugins/common/functions index 74750073f..2b13b9416 100755 --- a/plugins/common/functions +++ b/plugins/common/functions @@ -476,6 +476,56 @@ is_abs_path() { fi } +fn-cli-preserved-env-names() { + declare desc="names the environment variables the re-exec as the dokku user must carry" + + # every variable dokku honours from the environment has to be listed, or the child + # derives different values than the parent - during the docker image build these only + # ever exist in the environment. DOKKU_PID is the one exclusion: it is unset above so + # the child gets its own. DOKKU_PRESERVE_ENV carries anything else + echo "DOKKU_APP_NAME DOKKU_APPS_FORCE_DELETE DOKKU_DISABLE_TTY DOKKU_FORCE_TTY" \ + "DOKKU_GLOBAL_FLAGS DOKKU_PRESERVE_ENV DOKKU_QUIET_OUTPUT DOKKU_TRACE" \ + "FINGERPRINT NAME SSH_NAME SSH_ORIGINAL_COMMAND SSH_USER" \ + "DOCKER_BIN DOKKU_CNB_BUILDER DOKKU_EVENTS_LOGFILE DOKKU_HOST_ROOT DOKKU_IMAGE" \ + "DOKKU_INIT_SYSTEM DOKKU_LIB_HOST_ROOT DOKKU_LIB_PATH DOKKU_LOGS_DIR" \ + "DOKKU_LOGS_HOST_DIR DOKKU_ROOT DOKKU_SYSTEM_GROUP DOKKU_SYSTEM_USER" \ + "PLUGIN_AVAILABLE_PATH PLUGIN_CORE_AVAILABLE_PATH PLUGIN_CORE_ENABLED_PATH" \ + "PLUGIN_CORE_PATH PLUGIN_ENABLED_PATH PLUGIN_PATH" +} + +fn-sudo-preserve-env-flag() { + declare desc="prints the sudo flag that carries environment variables across a privilege drop" + local names=("$@") + local extra_names=() + local joined name + + # sudo-rs, the default sudo on ubuntu 25.10 and later, ignores -E and bare + # --preserve-env, so every variable that has to survive must be named + if [[ "${#names[@]}" -eq 0 ]]; then + for name in $(compgen -e); do + # preserving these would defeat -H and override the sudoers secure_path + case "$name" in + HOME | LOGNAME | MAIL | PATH | SHELL | USER | _ | SUDO_*) + continue + ;; + esac + names+=("$name") + done + fi + + if [[ -n "$DOKKU_PRESERVE_ENV" ]]; then + read -ra extra_names <<<"${DOKKU_PRESERVE_ENV//,/ }" + names+=("${extra_names[@]}") + fi + + if [[ "${#names[@]}" -eq 0 ]]; then + return 0 + fi + + printf -v joined "%s," "${names[@]}" + echo "--preserve-env=${joined%,}" +} + parse_args() { declare desc="top-level cli arg parser" local next_index=1 diff --git a/plugins/plugin/subcommands/trigger b/plugins/plugin/subcommands/trigger index 81065f147..4d9ac5989 100755 --- a/plugins/plugin/subcommands/trigger +++ b/plugins/plugin/subcommands/trigger @@ -1,13 +1,22 @@ #!/usr/bin/env bash set -eo pipefail [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" cmd-plugin-trigger() { declare desc="trigger an arbitrary plugin hook" declare cmd="plugin:trigger" [[ "$1" == "$cmd" ]] && shift 1 - sudo -u dokku -E -H plugn trigger "$@" + local sudo_args=(-u dokku) + local preserve_env_flag + preserve_env_flag="$(fn-sudo-preserve-env-flag)" + if [[ -n "$preserve_env_flag" ]]; then + sudo_args+=("$preserve_env_flag") + fi + sudo_args+=(-H) + + sudo "${sudo_args[@]}" plugn trigger "$@" } cmd-plugin-trigger "$@" diff --git a/tests/ci/sudo_preserve_env.sh b/tests/ci/sudo_preserve_env.sh new file mode 100755 index 000000000..51896556b --- /dev/null +++ b/tests/ci/sudo_preserve_env.sh @@ -0,0 +1,112 @@ +#!/usr/bin/env bash +# Checks that the environment dokku forwards across its privilege drop survives the +# local sudo implementation. sudo-rs, the default sudo on ubuntu 25.10 and later, +# ignores -E and bare --preserve-env, so dokku names every variable it needs. +set -eo pipefail + +PLUGIN_CORE_AVAILABLE_PATH="${PLUGIN_CORE_AVAILABLE_PATH:-$(dirname "$0")/../../plugins}" +export PLUGIN_CORE_AVAILABLE_PATH +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" + +FAILURES=0 + +fail() { + declare desc="report a failed check" + echo "not ok - $1" 1>&2 + FAILURES=$((FAILURES + 1)) +} + +pass() { + declare desc="report a passing check" + echo "ok - $1" +} + +report-whole-environment-support() { + declare desc="record whether this sudo honours -E, for the log" + local warning + + warning="$(DOKKU_TEST_CROSSING=1 sudo -E -u nobody true 2>&1 >/dev/null || true)" + if [[ -n "$warning" ]]; then + echo "# sudo -E is not supported here: $warning" + else + echo "# sudo -E is supported here" + fi +} + +check-named-list-crosses() { + declare desc="the named variables reach the target user" + local flag output + + flag="$(DOKKU_QUIET_OUTPUT=1 DOKKU_TRACE=1 SSH_USER=ci fn-sudo-preserve-env-flag DOKKU_QUIET_OUTPUT DOKKU_TRACE SSH_USER)" + output="$(DOKKU_QUIET_OUTPUT=1 DOKKU_TRACE=1 SSH_USER=ci sudo "$flag" -u nobody /usr/bin/env)" + + local name + for name in DOKKU_QUIET_OUTPUT=1 DOKKU_TRACE=1 SSH_USER=ci; do + if grep -qxF "$name" <<<"$output"; then + pass "$name crossed the privilege drop" + else + fail "$name did not cross the privilege drop" + fi + done +} + +check-named-list-is-silent() { + declare desc="no warning is written to stderr" + local flag stderr + + flag="$(SSH_USER=ci fn-sudo-preserve-env-flag SSH_USER)" + stderr="$(SSH_USER=ci sudo "$flag" -u nobody true 2>&1 >/dev/null || true)" + if [[ -z "$stderr" ]]; then + pass "no warning on stderr" + else + fail "unexpected stderr: $stderr" + fi +} + +check-absent-names-are-tolerated() { + declare desc="naming a variable that is not set is not an error" + local flag + + flag="$(fn-sudo-preserve-env-flag DOKKU_DEFINITELY_NOT_SET_HERE SSH_USER)" + if sudo "$flag" -u nobody true; then + pass "unset names are ignored" + else + fail "sudo rejected a list naming an unset variable" + fi +} + +check-home-still-wins() { + declare desc="-H is not defeated by the preserved list" + local flag output + local names=() + + read -ra names <<<"$(fn-cli-preserved-env-names)" + flag="$(fn-sudo-preserve-env-flag "${names[@]}")" + output="$(sudo "$flag" -H -u nobody /usr/bin/env)" + if grep -qxF "HOME=$(getent passwd nobody | cut -d: -f6)" <<<"$output"; then + pass "-H still sets HOME for the target user" + else + fail "HOME was not reset for the target user" + fi +} + +main() { + declare desc="run every check" + + echo "# sudo version:" + sudo --version | head -1 | sed -e 's/^/# /' + + report-whole-environment-support + check-named-list-crosses + check-named-list-is-silent + check-absent-names-are-tolerated + check-home-still-wins + + if [[ "$FAILURES" -gt 0 ]]; then + echo "$FAILURES check(s) failed" 1>&2 + exit 1 + fi + echo "all checks passed" +} + +main "$@" diff --git a/tests/unit/core_1.bats b/tests/unit/core_1.bats index b28d3c510..11cacfdca 100644 --- a/tests/unit/core_1.bats +++ b/tests/unit/core_1.bats @@ -120,3 +120,77 @@ teardown() { assert_line 0 "--label" assert_line 1 "a b" } + +@test "(common) [preserve-env] fn-sudo-preserve-env-flag emits only the requested names" { + run /bin/bash -c "source '$PLUGIN_CORE_AVAILABLE_PATH/common/functions'; fn-sudo-preserve-env-flag DOKKU_TRACE DOKKU_APP_NAME" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "--preserve-env=DOKKU_TRACE,DOKKU_APP_NAME" +} + +@test "(common) [preserve-env] fn-sudo-preserve-env-flag omits sudo managed names" { + run /bin/bash -c "source '$PLUGIN_CORE_AVAILABLE_PATH/common/functions'; export DOKKU_TEST_CROSSING=1; fn-sudo-preserve-env-flag | sed 's/^--preserve-env=//' | tr ',' '\\n' | grep -cx DOKKU_TEST_CROSSING" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "1" + + run /bin/bash -c "source '$PLUGIN_CORE_AVAILABLE_PATH/common/functions'; fn-sudo-preserve-env-flag | sed 's/^--preserve-env=//' | tr ',' '\\n' | grep -cxE 'HOME|LOGNAME|MAIL|PATH|SHELL|USER|_|SUDO_.*'" + echo "output: $output" + echo "status: $status" + assert_output "0" +} + +@test "(common) [preserve-env] fn-sudo-preserve-env-flag honors DOKKU_PRESERVE_ENV" { + run /bin/bash -c "source '$PLUGIN_CORE_AVAILABLE_PATH/common/functions'; DOKKU_PRESERVE_ENV=ALPHA,BETA fn-sudo-preserve-env-flag DOKKU_TRACE" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "--preserve-env=DOKKU_TRACE,ALPHA,BETA" + + run /bin/bash -c "source '$PLUGIN_CORE_AVAILABLE_PATH/common/functions'; DOKKU_PRESERVE_ENV=ALPHA fn-sudo-preserve-env-flag | sed 's/^--preserve-env=//' | tr ',' '\\n' | grep -cx ALPHA" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "1" +} + +@test "(common) [preserve-env] fn-cli-preserved-env-names covers every parse_args export" { + run /bin/bash -c "source '$PLUGIN_CORE_AVAILABLE_PATH/common/functions'; fn-cli-preserved-env-names | tr ' ' '\\n' | grep -cxE 'DOKKU_APP_NAME|DOKKU_APPS_FORCE_DELETE|DOKKU_GLOBAL_FLAGS|DOKKU_PRESERVE_ENV|DOKKU_QUIET_OUTPUT|DOKKU_TRACE|SSH_USER'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "7" +} + +@test "(common) [preserve-env] fn-cli-preserved-env-names covers the docker image build variables" { + # neither is read by the dokku script itself, so the derivation test below does not + # catch them. both exist only in the environment while the docker image is built + run /bin/bash -c "source '$PLUGIN_CORE_AVAILABLE_PATH/common/functions'; fn-cli-preserved-env-names | tr ' ' '\\n' | grep -cxE 'DOKKU_INIT_SYSTEM|DOKKU_LIB_HOST_ROOT'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "2" +} + +@test "(common) [preserve-env] every environment-overridable dokku variable is preserved" { + # test_helper leaves these unexported, so the single-quoted script below cannot see + # them without this + export PLUGIN_CORE_AVAILABLE_PATH + export DOKKU_SOURCE="${BATS_TEST_DIRNAME}/../../dokku" + run /bin/bash -c 'source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions"; comm -23 <(grep -oE "[{][A-Z_]+:=" "$DOKKU_SOURCE" | tr -cd "A-Z_\n" | sort -u | grep -vx DOKKU_PID) <(fn-cli-preserved-env-names | tr " " "\n" | sort -u)' + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + unset DOKKU_SOURCE +} + +@test "(common) [preserve-env] parse_args records global flags for replay" { + run /bin/bash -c "source '$PLUGIN_CORE_AVAILABLE_PATH/common/functions'; parse_args --label=a=b run foo env; echo \"\$DOKKU_GLOBAL_FLAGS\"" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "--label=a=b" +} diff --git a/tests/unit/core_3.bats b/tests/unit/core_3.bats new file mode 100644 index 000000000..832bb51b1 --- /dev/null +++ b/tests/unit/core_3.bats @@ -0,0 +1,105 @@ +#!/usr/bin/env bats + +load test_helper + +setup() { + global_setup + create_app +} + +teardown() { + destroy_app + global_teardown +} + +@test "(core) [global-flags] --quiet crosses the privilege drop" { + run /bin/bash -c "dokku apps:list" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "My Apps" + + run /bin/bash -c "dokku --quiet apps:list" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_not_contains "My Apps" + assert_output_contains "$TEST_APP" +} + +@test "(core) [global-flags] --trace crosses the privilege drop" { + run /bin/bash -c "dokku --trace apps:list 2>&1 >/dev/null" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "execute_dokku_cmd" +} + +@test "(core) [global-flags] --force crosses the privilege drop" { + local force_app="${TEST_APP}-force" + run /bin/bash -c "dokku apps:create $force_app" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku --force apps:destroy $force_app &1 >/dev/null" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" +} + +@test "(core) [global-flags] the caller environment crosses the privilege drop" { + run /bin/bash -c "DOKKU_QUIET_OUTPUT=1 dokku apps:list" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_not_contains "My Apps" + assert_output_contains "$TEST_APP" +} + +@test "(core) [global-flags] an unlisted variable only crosses when DOKKU_PRESERVE_ENV names it" { + # DOKKU_TRACE is set in the environment rather than passed as --trace so that the + # parent traces too: the sudo line naming the forwarded variables is only emitted there + run /bin/bash -c "DOKKU_TRACE=1 DOKKU_TEST_CROSSING=1 dokku apps:list 2>&1 >/dev/null | grep -o -- '--preserve-env=[^ ]*' | head -1" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "--preserve-env=" + assert_output_not_contains "DOKKU_TEST_CROSSING" + + run /bin/bash -c "DOKKU_TRACE=1 DOKKU_PRESERVE_ENV=DOKKU_TEST_CROSSING DOKKU_TEST_CROSSING=1 dokku apps:list 2>&1 >/dev/null | grep -o -- '--preserve-env=[^ ]*' | head -1" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "DOKKU_TEST_CROSSING" +} + +@test "(core) [global-flags] the system sudo supports a named preserve-env list" { + run /bin/bash -c "DOKKU_TEST_CROSSING=1 sudo -u dokku --preserve-env=DOKKU_TEST_CROSSING -H /usr/bin/env | grep -cxE 'DOKKU_TEST_CROSSING=1|HOME=/home/dokku'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "2" +} diff --git a/tests/unit/plugin.bats b/tests/unit/plugin.bats index c4037754e..fe15deab9 100644 --- a/tests/unit/plugin.bats +++ b/tests/unit/plugin.bats @@ -116,7 +116,7 @@ teardown() { echo "status: $status" assert_success - run /bin/bash -c "sudo -E -u nobody dokku plugin:uninstall $TEST_PLUGIN_NAME" + run /bin/bash -c "sudo -u nobody dokku plugin:uninstall $TEST_PLUGIN_NAME" echo "output: $output" echo "status: $status" assert_failure @@ -158,7 +158,7 @@ teardown() { echo "status: $status" assert_success - run /bin/bash -c "sudo -E -u nobody dokku plugin:uninstall $TEST_PLUGIN_NAME" + run /bin/bash -c "sudo -u nobody dokku plugin:uninstall $TEST_PLUGIN_NAME" echo "output: $output" echo "status: $status" assert_failure @@ -212,7 +212,7 @@ teardown() { } @test "(plugin) plugin:install, plugin:disable, plugin:uninstall as non-root user failure" { - run /bin/bash -c "sudo -E -u nobody dokku plugin:install $TEST_PLUGIN_GIT_REPO" + run /bin/bash -c "sudo -u nobody dokku plugin:install $TEST_PLUGIN_GIT_REPO" echo "output: $output" echo "status: $status" assert_failure @@ -227,7 +227,7 @@ teardown() { echo "status: $status" assert_success - run /bin/bash -c "sudo -E -u nobody dokku plugin:disable $TEST_PLUGIN_NAME" + run /bin/bash -c "sudo -u nobody dokku plugin:disable $TEST_PLUGIN_NAME" echo "output: $output" echo "status: $status" assert_failure @@ -372,3 +372,10 @@ teardown() { assert_output_contains "root" "0" assert_output_contains "dokku" "3" } + +@test "(plugin:trigger) forwards the caller environment across the privilege drop" { + run /bin/bash -c "DOKKU_TEST_CROSSING=1 DOKKU_TRACE=1 dokku plugin:trigger app-list 2>&1 >/dev/null | grep -q -- '--preserve-env=[^ ]*DOKKU_TEST_CROSSING'" + echo "output: $output" + echo "status: $status" + assert_success +} diff --git a/tests/unit/resource_3.bats b/tests/unit/resource_3.bats index 174d2084d..fcf3423e8 100644 --- a/tests/unit/resource_3.bats +++ b/tests/unit/resource_3.bats @@ -101,6 +101,8 @@ teardown() { echo "status: $status" assert_success + # DOKKU_OMIT_RESOURCE_ARGS has to survive the privilege drop in + # plugins/plugin/subcommands/trigger, so this also covers that forwarding run_plugn_trigger DOKKU_OMIT_RESOURCE_ARGS=1 docker-args-process-build "$TEST_APP" herokuish echo "output: $output" echo "status: $status" diff --git a/tests/unit/run_2.bats b/tests/unit/run_2.bats index 86cf27e70..0dc6203b9 100644 --- a/tests/unit/run_2.bats +++ b/tests/unit/run_2.bats @@ -35,6 +35,9 @@ teardown() { echo "status: $status" assert_success + # the leading --label is replayed out of DOKKU_GLOBAL_FLAGS by + # plugins/scheduler-docker-local/scheduler-run, so this also covers that variable + # surviving the re-exec as the dokku user RANDOM_RUN_CID="$(dokku --label=com.dokku.test-label=value run:detached $TEST_APP sleep 300)" run /bin/bash -c "docker inspect -f '{{ .State.Status }}' $RANDOM_RUN_CID" echo "output: $output"