core: open more fields up to StartTransientUnit via Varlink (#43516)
This is split out of #43058 but makes a ton of sense on its own.
This commit is contained in:
@@ -211,6 +211,99 @@ varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
timeout 30 bash -c 'until systemctl is-active varlink-transient-wd-home.service; do sleep 0.5; done'
|
||||
systemctl show -P WorkingDirectory varlink-transient-wd-home.service | grep '^~$' >/dev/null
|
||||
|
||||
# Exec.StandardInput/StandardOutput/StandardError enum properties and Exec.TTYPath
|
||||
defer_transient_cleanup varlink-transient-stdio.service
|
||||
result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-stdio.service","Exec":{"StandardInput":"null","StandardOutput":"null","StandardError":"journal","TTYPath":"/dev/tty2"},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}')
|
||||
echo "$result" | jq -e '.context.Exec.StandardInput == "null"'
|
||||
echo "$result" | jq -e '.context.Exec.StandardOutput == "null"'
|
||||
echo "$result" | jq -e '.context.Exec.StandardError == "journal"'
|
||||
echo "$result" | jq -e '.context.Exec.TTYPath == "/dev/tty2"'
|
||||
timeout 30 bash -c 'until systemctl is-active varlink-transient-stdio.service; do sleep 0.5; done'
|
||||
systemctl show -P StandardInput varlink-transient-stdio.service | grep '^null$' >/dev/null
|
||||
systemctl show -P StandardOutput varlink-transient-stdio.service | grep '^null$' >/dev/null
|
||||
systemctl show -P StandardError varlink-transient-stdio.service | grep '^journal$' >/dev/null
|
||||
systemctl show -P TTYPath varlink-transient-stdio.service | grep '^/dev/tty2$' >/dev/null
|
||||
# "null" is also the default for StandardInput=, hence additionally verify it was written explicitly
|
||||
fragment=$(systemctl show -P FragmentPath varlink-transient-stdio.service)
|
||||
grep '^StandardInput=null$' "$fragment" >/dev/null
|
||||
|
||||
# The combined output types are spelled with a "+" in unit files, but with a "_" in the Varlink IDL (and
|
||||
# hence in our own replies). The latter spelling must be accepted, so that replies can be fed back in.
|
||||
defer_transient_cleanup varlink-transient-stdio-combined.service
|
||||
result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-stdio-combined.service","Exec":{"StandardOutput":"kmsg_console","StandardError":"journal_console"},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}')
|
||||
echo "$result" | jq -e '.context.Exec.StandardOutput == "kmsg_console"'
|
||||
echo "$result" | jq -e '.context.Exec.StandardError == "journal_console"'
|
||||
timeout 30 bash -c 'until systemctl is-active varlink-transient-stdio-combined.service; do sleep 0.5; done'
|
||||
systemctl show -P StandardOutput varlink-transient-stdio-combined.service | grep '^kmsg+console$' >/dev/null
|
||||
systemctl show -P StandardError varlink-transient-stdio-combined.service | grep '^journal+console$' >/dev/null
|
||||
|
||||
# Kill.KillMode/Kill.SendSIGHUP
|
||||
defer_transient_cleanup varlink-transient-kill.service
|
||||
result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-kill.service","Kill":{"KillMode":"process","SendSIGHUP":true},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}')
|
||||
echo "$result" | jq -e '.context.Kill.KillMode == "process"'
|
||||
echo "$result" | jq -e '.context.Kill.SendSIGHUP == true'
|
||||
timeout 30 bash -c 'until systemctl is-active varlink-transient-kill.service; do sleep 0.5; done'
|
||||
systemctl show -P KillMode varlink-transient-kill.service | grep '^process$' >/dev/null
|
||||
systemctl show -P SendSIGHUP varlink-transient-kill.service | grep '^yes$' >/dev/null
|
||||
|
||||
# ExecStart execution flags, i.e. the Varlink counterparts of the "-", ":", "|", "+" and "!" prefixes of
|
||||
# unit file ExecStart= lines. First the ones observable from an unprivileged unit:
|
||||
# - ignoreFailure: a failing command does not fail the unit
|
||||
# - noEnvExpand: "${VAR}" in the arguments is passed as-is rather than expanded
|
||||
# - viaShell: the command line is handed to the user's shell via "-c" (argv[0] is normalized to "sh")
|
||||
rm -f /tmp/varlink-transient-execflags-*
|
||||
defer_transient_cleanup varlink-transient-execflags.service
|
||||
# shellcheck disable=SC2016
|
||||
result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-execflags.service","Exec":{"Environment":["FLAGTEST=expanded"]},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[
|
||||
{"path":"/bin/false","ignoreFailure":true},
|
||||
{"path":"/usr/bin/touch","arguments":["touch","/tmp/varlink-transient-execflags-${FLAGTEST}"]},
|
||||
{"path":"/usr/bin/touch","arguments":["touch","/tmp/varlink-transient-execflags-${FLAGTEST}"],"noEnvExpand":true},
|
||||
{"path":"/bin/sh","arguments":["sh","touch","/tmp/varlink-transient-execflags-shell"],"viaShell":true}]}}}')
|
||||
echo "$result" | jq -e '.context.Service.ExecStart[0].ignoreFailure == true'
|
||||
echo "$result" | jq -e '.context.Service.ExecStart[1].noEnvExpand == false'
|
||||
echo "$result" | jq -e '.context.Service.ExecStart[2].noEnvExpand == true'
|
||||
echo "$result" | jq -e '.context.Service.ExecStart[3].viaShell == true'
|
||||
echo "$result" | jq -e '.context.Service.ExecStart[3].path == "/bin/sh"'
|
||||
echo "$result" | jq -e '.context.Service.ExecStart[3].arguments[0] == "sh"'
|
||||
# The unit only becomes active once all ExecStart= commands succeeded (or were allowed to fail)
|
||||
timeout 30 bash -c 'until systemctl is-active varlink-transient-execflags.service; do sleep 0.5; done'
|
||||
test -e /tmp/varlink-transient-execflags-expanded
|
||||
# shellcheck disable=SC2016
|
||||
test -e '/tmp/varlink-transient-execflags-${FLAGTEST}'
|
||||
test -e /tmp/varlink-transient-execflags-shell
|
||||
# The flags must show up as the matching prefix characters in the transient unit file
|
||||
fragment=$(systemctl show -P FragmentPath varlink-transient-execflags.service)
|
||||
grep -E '^ExecStart=-"/bin/false"$' "$fragment" >/dev/null
|
||||
grep -E '^ExecStart=:' "$fragment" >/dev/null
|
||||
grep -E '^ExecStart=\|' "$fragment" >/dev/null
|
||||
rm -f /tmp/varlink-transient-execflags-*
|
||||
|
||||
# Now the privilege related ones, in a unit running as an unprivileged user:
|
||||
# - privileged/noSetuid: the command runs with full privileges, i.e. can write where the user cannot
|
||||
# - (neither): the command runs as the user, and fails to write there (tolerated via ignoreFailure)
|
||||
rm -f /run/varlink-transient-execflags-*
|
||||
defer_transient_cleanup varlink-transient-execflags-priv.service
|
||||
result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-execflags-priv.service","Exec":{"User":"nobody"},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[
|
||||
{"path":"/usr/bin/touch","arguments":["touch","/run/varlink-transient-execflags-plain"],"ignoreFailure":true},
|
||||
{"path":"/usr/bin/touch","arguments":["touch","/run/varlink-transient-execflags-privileged"],"privileged":true},
|
||||
{"path":"/usr/bin/touch","arguments":["touch","/run/varlink-transient-execflags-nosetuid"],"noSetuid":true}]}}}')
|
||||
echo "$result" | jq -e '.context.Service.ExecStart[0].privileged == false'
|
||||
echo "$result" | jq -e '.context.Service.ExecStart[1].privileged == true'
|
||||
echo "$result" | jq -e '.context.Service.ExecStart[2].noSetuid == true'
|
||||
timeout 30 bash -c 'until systemctl is-active varlink-transient-execflags-priv.service; do sleep 0.5; done'
|
||||
test ! -e /run/varlink-transient-execflags-plain
|
||||
test -e /run/varlink-transient-execflags-privileged
|
||||
test -e /run/varlink-transient-execflags-nosetuid
|
||||
fragment=$(systemctl show -P FragmentPath varlink-transient-execflags-priv.service)
|
||||
grep -E '^ExecStart=\+' "$fragment" >/dev/null
|
||||
grep -E '^ExecStart=!' "$fragment" >/dev/null
|
||||
rm -f /run/varlink-transient-execflags-*
|
||||
|
||||
# Exec.SetCredential: pass a credential and verify the running process can read it
|
||||
defer_transient_cleanup varlink-transient-cred.service
|
||||
CRED_VALUE_B64=$(printf 'secret-value' | base64 -w0)
|
||||
@@ -310,6 +403,11 @@ defer_transient_cleanup varlink-transient-badpath.service
|
||||
expect_invalid_parameter \
|
||||
'{"context":{"ID":"varlink-transient-badpath.service","Service":{"Type":"simple","ExecStart":[{"path":""}]}}}' \
|
||||
"Service.ExecStart"
|
||||
# The 'privileged' and 'noSetuid' execution flags are mutually exclusive
|
||||
defer_transient_cleanup varlink-transient-badflags.service
|
||||
expect_invalid_parameter \
|
||||
'{"context":{"ID":"varlink-transient-badflags.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true","privileged":true,"noSetuid":true}]}}}' \
|
||||
"Service.ExecStart"
|
||||
# Relative WorkingDirectory path is rejected
|
||||
defer_transient_cleanup varlink-transient-bad-wd.service
|
||||
expect_invalid_parameter \
|
||||
@@ -340,6 +438,33 @@ defer_transient_cleanup varlink-transient-bad-rd.service
|
||||
expect_invalid_parameter \
|
||||
'{"context":{"ID":"varlink-transient-bad-rd.service","Exec":{"RootDirectory":"relative/path"},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \
|
||||
"Exec.RootDirectory"
|
||||
# Note that an unparsable enum value (e.g. StandardOutput=bogus) is rejected by the Varlink IDL
|
||||
# validation layer before the JSON dispatch even runs, hence there's no point in testing pid1's
|
||||
# dispatch-level enum handling here — and doing so would trip post.sh's "didn't pass validation"
|
||||
# journal check, since the IDL layer logs about the rejection.
|
||||
# Relative TTYPath is rejected
|
||||
defer_transient_cleanup varlink-transient-bad-ttypath.service
|
||||
expect_invalid_parameter \
|
||||
'{"context":{"ID":"varlink-transient-bad-ttypath.service","Exec":{"TTYPath":"relative/tty"},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \
|
||||
"Exec.TTYPath"
|
||||
# The file based stdio types need a companion path, and StandardInput=data a companion payload, neither of
|
||||
# which can be passed yet, hence they are rejected
|
||||
for t in file append truncate; do
|
||||
defer_transient_cleanup "varlink-transient-bad-stdout-$t.service"
|
||||
expect_invalid_parameter \
|
||||
"{\"context\":{\"ID\":\"varlink-transient-bad-stdout-$t.service\",\"Exec\":{\"StandardOutput\":\"$t\"},\"Service\":{\"Type\":\"oneshot\",\"ExecStart\":[{\"path\":\"/bin/true\"}]}}}" \
|
||||
"Exec.StandardOutput"
|
||||
done
|
||||
defer_transient_cleanup varlink-transient-bad-stderr.service
|
||||
expect_invalid_parameter \
|
||||
'{"context":{"ID":"varlink-transient-bad-stderr.service","Exec":{"StandardError":"file"},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \
|
||||
"Exec.StandardError"
|
||||
for t in file data; do
|
||||
defer_transient_cleanup "varlink-transient-bad-stdin-$t.service"
|
||||
expect_invalid_parameter \
|
||||
"{\"context\":{\"ID\":\"varlink-transient-bad-stdin-$t.service\",\"Exec\":{\"StandardInput\":\"$t\"},\"Service\":{\"Type\":\"oneshot\",\"ExecStart\":[{\"path\":\"/bin/true\"}]}}}" \
|
||||
"Exec.StandardInput"
|
||||
done
|
||||
# Invalid credential ID
|
||||
defer_transient_cleanup varlink-transient-bad-cred-id.service
|
||||
expect_invalid_parameter \
|
||||
@@ -353,6 +478,9 @@ expect_invalid_parameter \
|
||||
# Exec on a unit type without an exec context (.slice) is rejected
|
||||
varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-exec.slice","Exec":{"WorkingDirectory":{"path":"/tmp","missingOK":false}}}}' |& grep "io.systemd.Unit.UnitTypeNotSupported"
|
||||
# Same for Kill on a unit type without a kill context
|
||||
varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-kill.slice","Kill":{"KillMode":"process"}}}' |& grep "io.systemd.Unit.UnitTypeNotSupported"
|
||||
# Unknown field in Exec is rejected as PropertyNotSupported
|
||||
defer_transient_cleanup varlink-transient-unknown-exec.service
|
||||
unsupported_exec=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
@@ -366,6 +494,12 @@ unsupported_service=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTra
|
||||
'{"context":{"ID":"varlink-transient-unknown-service.service","Service":{"Type":"oneshot","Restart":"always","ExecStart":[{"path":"/bin/true"}]}}}' 2>&1 || true)
|
||||
echo "$unsupported_service" | grep "io.systemd.Unit.PropertyNotSupported"
|
||||
echo "$unsupported_service" | grep "Service.Restart"
|
||||
# Same for a Kill field
|
||||
defer_transient_cleanup varlink-transient-unknown-kill.service
|
||||
unsupported_kill=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-unknown-kill.service","Kill":{"KillSignal":"SIGINT"},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' 2>&1 || true)
|
||||
echo "$unsupported_kill" | grep "io.systemd.Unit.PropertyNotSupported"
|
||||
echo "$unsupported_kill" | grep "Kill.KillSignal"
|
||||
set -o pipefail
|
||||
|
||||
transient_cleanup
|
||||
|
||||
Reference in New Issue
Block a user