Skip to content

Commit

Permalink
Add some needed exec options: (#99)
Browse files Browse the repository at this point in the history
* Add some needed exec options:

Run as a specific user/group
Append specific env vars to the otherwise clean environment.

Tests for everything

* Simplify uid/gid by prepopulating with our euid/gid
  • Loading branch information
sfc-gh-jchacon authored Apr 12, 2022
1 parent 4a7e8ca commit 3600ab3
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 4 deletions.
48 changes: 45 additions & 3 deletions services/util/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,12 @@ import (
"context"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"syscall"

"github.com/Snowflake-Labs/sansshell/proxy/proxy"
"github.com/go-logr/logr"
Expand Down Expand Up @@ -53,12 +55,14 @@ type CommandRun struct {
ExitCode int
}

// There's only one now but compose into a builder pattern for Options so
// it's easy to add more (such as dropping permissions, etc).
// A builder pattern for Options so it's easy to add various ones (such as dropping permissions, etc).
type cmdOptions struct {
failOnStderr bool
stdoutMax uint
stderrMax uint
env []string
uid uint32
gid uint32
}

// Option will run the apply operation to change required checking/state
Expand Down Expand Up @@ -101,6 +105,28 @@ func StderrMax(max uint) Option {
})
}

// CommandUser is an option which sets the uid for the Command to run as.
func CommandUser(uid uint32) Option {
return optionfunc(func(o *cmdOptions) {
o.uid = uid
})
}

// CommandGroup is an option which sets the gid for the Command to run as.
func CommandGroup(gid uint32) Option {
return optionfunc(func(o *cmdOptions) {
o.gid = gid
})
}

// EnvVar is an option which sets an environment variable for the sub-processes.
// evar should be of the form foo=bar
func EnvVar(evar string) Option {
return optionfunc(func(o *cmdOptions) {
o.env = append(o.env, evar)
})
}

// DefRunBufLimit is the default limit we'll buffer for stdout/stderr from RunCommand exec'ing
// a process.
const DefRunBufLimit = 10 * 1024 * 1024
Expand Down Expand Up @@ -182,9 +208,13 @@ func RunCommand(ctx context.Context, bin string, args []string, opts ...Option)
return nil, status.Errorf(codes.InvalidArgument, "%s is not a clean path", bin)
}

euid := uint32(os.Geteuid())
gid := uint32(os.Getgid())
options := &cmdOptions{
stdoutMax: DefRunBufLimit,
stderrMax: DefRunBufLimit,
uid: euid,
gid: gid,
}
for _, opt := range opts {
opt.apply(options)
Expand All @@ -202,7 +232,19 @@ func RunCommand(ctx context.Context, bin string, args []string, opts ...Option)
cmd.Stdin = nil
// Set to an empty slice to get an empty environment. Nil means inherit.
cmd.Env = []string{}

// Now append any we received.
cmd.Env = append(cmd.Env, options.env...)

// Set uid/gid if needed for the sub-process to run under.
// Only do this if it's different than our current ones since
// attempting to setuid/gid() to even your current values is EPERM.
if options.uid != euid || options.gid != gid {
cmd.SysProcAttr = &syscall.SysProcAttr{
Credential: &syscall.Credential{},
}
cmd.SysProcAttr.Credential.Uid = options.uid
cmd.SysProcAttr.Credential.Gid = options.gid
}
logger.Info("executing local command", "cmd", cmd.String())
run.Error = cmd.Run()
run.ExitCode = cmd.ProcessState.ExitCode()
Expand Down
31 changes: 30 additions & 1 deletion services/util/util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,12 @@ func TestRunCommand(t *testing.T) {
args []string
wantErr bool
returnCodeNonZero bool
uid uint32
gid uint32
stdout string
stderr string
stderrIsError bool
env []string
}{
{
name: "Not absolute path",
Expand Down Expand Up @@ -71,11 +74,29 @@ func TestRunCommand(t *testing.T) {
bin: testutil.ResolvePath(t, "env"),
stdout: "",
},
{
name: "verify env vars",
bin: testutil.ResolvePath(t, "env"),
stdout: "FOO=bar\nBAZ=e\n",
env: []string{"FOO=bar", "BAZ=e"},
},
{
name: "error codes",
bin: testutil.ResolvePath(t, "false"),
returnCodeNonZero: true,
},
{
name: "set uid (will fail)",
bin: testutil.ResolvePath(t, "env"),
uid: 99,
returnCodeNonZero: true,
},
{
name: "set gid (will fail)",
bin: testutil.ResolvePath(t, "env"),
gid: 99,
returnCodeNonZero: true,
},
} {
tc := tc
t.Run(tc.name, func(t *testing.T) {
Expand All @@ -86,7 +107,15 @@ func TestRunCommand(t *testing.T) {
if tc.stderrIsError {
opts = append(opts, FailOnStderr())
}

for _, e := range tc.env {
opts = append(opts, EnvVar(e))
}
if tc.uid != 0 {
opts = append(opts, CommandUser(tc.uid))
}
if tc.gid != 0 {
opts = append(opts, CommandGroup(tc.gid))
}
run, err := RunCommand(context.Background(), tc.bin, tc.args, opts...)
t.Logf("%s: response: %+v", tc.name, run)
t.Logf("%s: error: %v", tc.name, err)
Expand Down

0 comments on commit 3600ab3

Please sign in to comment.