From 38a2321cd2d115359409cdf7e1e27600e78f5fd7 Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Fri, 22 Apr 2022 14:52:54 +0200 Subject: [PATCH] systemd: add systemd.Run() wrapper for systemd-run This commit adds a new `systemd.Run()` function that implements a wrapper around `systemd-run`. This will be used to spawn commands that use the `--keyring-mode=inherit` when interacting with the kernel keyring. --- kernel/fde/cmd_helper.go | 3 ++- systemd/emulation.go | 4 ++++ systemd/systemd.go | 47 ++++++++++++++++++++++++++++++++++++ systemd/systemd_test.go | 51 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 104 insertions(+), 1 deletion(-) diff --git a/kernel/fde/cmd_helper.go b/kernel/fde/cmd_helper.go index d02fc940f4c..b70a9f07a87 100644 --- a/kernel/fde/cmd_helper.go +++ b/kernel/fde/cmd_helper.go @@ -79,7 +79,8 @@ func runFDEinitramfsHelper(name string, stdin []byte) (output []byte, err error) } } - // TODO: put this into a new "systemd/run" package + // TODO: use the new systemd.Run() interface once it supports + // running without dbus (i.e. supports running without --pipe) cmd := exec.Command( "systemd-run", "--collect", diff --git a/systemd/emulation.go b/systemd/emulation.go index 99b0eb82fbf..3aa97e7a58c 100644 --- a/systemd/emulation.go +++ b/systemd/emulation.go @@ -222,3 +222,7 @@ func (s *emulation) Mount(what, where string, options ...string) error { func (s *emulation) Umount(whatOrWhere string) error { return ¬ImplementedError{"Umount"} } + +func (s *emulation) Run(command []string, opts *RunOptions) ([]byte, error) { + return nil, ¬ImplementedError{"Run"} +} diff --git a/systemd/systemd.go b/systemd/systemd.go index 28570886bbc..47a7c793816 100644 --- a/systemd/systemd.go +++ b/systemd/systemd.go @@ -358,6 +358,25 @@ type Systemd interface { // threads if enabled, etc) part of the unit, which can be a service or a // slice. CurrentTasksCount(unit string) (uint64, error) + // Run a command + Run(command []string, opts *RunOptions) ([]byte, error) +} + +// KeyringMode describes how the kernel keyring is setup, see systemd.exec(5) +type KeyringMode string + +const ( + KeyringModeInherit KeyringMode = "inherit" + KeyringModePrivate KeyringMode = "private" + KeyringModeShared KeyringMode = "shared" +) + +// RunOptions can be passed to systemd.Run() +type RunOptions struct { + // XXX: alternative we could just have `Propertes []string` here + // and let the caller do the keyring setup but feels a bit loose + KeyringMode KeyringMode + Stdin io.Reader } // A Log is a single entry in the systemd journal. @@ -1534,3 +1553,31 @@ func (s *systemd) Umount(whatOrWhere string) error { } return nil } + +// Run runs the given command via "sytemd-run" and returns the output +// or an error if the command fails. +func (s *systemd) Run(command []string, opts *RunOptions) ([]byte, error) { + if opts == nil { + opts = &RunOptions{} + } + runArgs := []string{ + "--wait", + "--pipe", + "--collect", + "--service-type=exec", + "--quiet", + } + if opts.KeyringMode != "" { + runArgs = append(runArgs, fmt.Sprintf("--property=KeyringMode=%v", opts.KeyringMode)) + } + runArgs = append(runArgs, "--") + runArgs = append(runArgs, command...) + cmd := exec.Command("systemd-run", runArgs...) + cmd.Stdin = opts.Stdin + + output, err := cmd.CombinedOutput() + if err != nil { + return nil, fmt.Errorf("cannot run %q: %v", command, osutil.OutputErr(output, err)) + } + return output, nil +} diff --git a/systemd/systemd_test.go b/systemd/systemd_test.go index ef94ba03801..d5c77f17672 100644 --- a/systemd/systemd_test.go +++ b/systemd/systemd_test.go @@ -1953,6 +1953,57 @@ func (s *SystemdTestSuite) TestInactiveEnterTimestampMalformedMore(c *C) { c.Check(stamp.IsZero(), Equals, true) } +func (s *SystemdTestSuite) TestSystemdRunError(c *C) { + sr := testutil.MockCommand(c, "systemd-run", `echo "fail"; exit 11`) + defer sr.Restore() + + sysd := New(SystemMode, s.rep) + output, err := sysd.Run([]string{"bad-cmd", "arg1"}, nil) + c.Check(output, IsNil) + c.Assert(err, ErrorMatches, `cannot run \["bad-cmd" "arg1"\]: fail`) +} + +func (s *SystemdTestSuite) TestSystemdRunHappy(c *C) { + sr := testutil.MockCommand(c, "systemd-run", `echo "happy output" && >&2 echo "to stderr"`) + defer sr.Restore() + + sysd := New(SystemMode, s.rep) + output, err := sysd.Run([]string{"happy-cmd", "arg1"}, nil) + c.Check(string(output), Equals, "happy output\nto stderr\n") + c.Check(err, IsNil) + c.Check(sr.Calls(), DeepEquals, [][]string{ + {"systemd-run", "--wait", "--pipe", "--collect", "--service-type=exec", "--quiet", "--", "happy-cmd", "arg1"}, + }) +} + +func (s *SystemdTestSuite) TestSystemdRunHappyWithStdin(c *C) { + sr := testutil.MockCommand(c, "systemd-run", `echo "some output" && cat - `) + defer sr.Restore() + + sysd := New(SystemMode, s.rep) + opts := &RunOptions{Stdin: bytes.NewBufferString("stdin input\n")} + output, err := sysd.Run([]string{"cmd-with-stdin", "arg1"}, opts) + c.Check(string(output), Equals, "some output\nstdin input\n") + c.Check(err, IsNil) + c.Check(sr.Calls(), DeepEquals, [][]string{ + {"systemd-run", "--wait", "--pipe", "--collect", "--service-type=exec", "--quiet", "--", "cmd-with-stdin", "arg1"}, + }) +} + +func (s *SystemdTestSuite) TestSystemdRunKeyringMode(c *C) { + sr := testutil.MockCommand(c, "systemd-run", `echo "happy output"`) + defer sr.Restore() + + sysd := New(SystemMode, s.rep) + opts := &RunOptions{KeyringMode: KeyringModePrivate} + output, err := sysd.Run([]string{"happy-cmd", "arg1"}, opts) + c.Check(string(output), Equals, "happy output\n") + c.Check(err, IsNil) + c.Check(sr.Calls(), DeepEquals, [][]string{ + {"systemd-run", "--wait", "--pipe", "--collect", "--service-type=exec", "--quiet", "--property=KeyringMode=private", "--", "happy-cmd", "arg1"}, + }) +} + type systemdErrorSuite struct{} var _ = Suite(&systemdErrorSuite{})