diff --git a/kyaml/fn/runtime/container/container.go b/kyaml/fn/runtime/container/container.go index b9468c7b7..b20032c49 100644 --- a/kyaml/fn/runtime/container/container.go +++ b/kyaml/fn/runtime/container/container.go @@ -5,7 +5,6 @@ package container import ( "fmt" - "os/user" runtimeexec "sigs.k8s.io/kustomize/kyaml/fn/runtime/exec" "sigs.k8s.io/kustomize/kyaml/fn/runtime/runtimeutil" @@ -185,28 +184,9 @@ func (c *Filter) getCommand() (string, []string) { return "docker", a } -// getUIDGID will return "nobody" if asCurrentUser is false. Otherwise -// return "uid:gid" according to current user who runs the command. -func getUIDGID(asCurrentUser bool) (string, error) { - if !asCurrentUser { - return "nobody", nil - } - - u, err := user.Current() - if err != nil { - return "", err - } - return fmt.Sprintf("%s:%s", u.Uid, u.Gid), nil -} - // NewContainer returns a new container filter -func NewContainer(spec runtimeutil.ContainerSpec, asCurrentUser bool) (Filter, error) { - f := Filter{ContainerSpec: spec} - u, err := getUIDGID(asCurrentUser) - if err != nil { - return f, err - } - f.UIDGID = u +func NewContainer(spec runtimeutil.ContainerSpec, uidgid string) (Filter, error) { + f := Filter{ContainerSpec: spec, UIDGID: uidgid} return f, nil } diff --git a/kyaml/fn/runtime/container/container_test.go b/kyaml/fn/runtime/container/container_test.go index 903fc92b3..549b018b6 100644 --- a/kyaml/fn/runtime/container/container_test.go +++ b/kyaml/fn/runtime/container/container_test.go @@ -6,7 +6,6 @@ package container import ( "bytes" "fmt" - "os/user" "testing" "github.com/stretchr/testify/assert" @@ -16,16 +15,12 @@ import ( ) func TestFilter_setupExec(t *testing.T) { - u, err := user.Current() - if err != nil { - t.Fatal(err) - } var tests = []struct { name string functionConfig string expectedArgs []string containerSpec runtimeutil.ContainerSpec - asCurrentUser bool + UIDGID string }{ { name: "command", @@ -45,6 +40,7 @@ metadata: containerSpec: runtimeutil.ContainerSpec{ Image: "example.com:version", }, + UIDGID: "nobody", }, { @@ -62,12 +58,11 @@ metadata: "--user", "nobody", "--security-opt=no-new-privileges", }, - instance: NewContainer( - runtimeutil.ContainerSpec{ - Image: "example.com:version", - Network: true, - }, + containerSpec: runtimeutil.ContainerSpec{ + Image: "example.com:version", + Network: true, }, + UIDGID: "nobody", }, { @@ -98,6 +93,7 @@ metadata: {MountType: "tmpfs", Src: "", DstPath: "/local/"}, }, }, + UIDGID: "nobody", }, { name: "as current user", @@ -111,13 +107,13 @@ metadata: "--rm", "-i", "-a", "STDIN", "-a", "STDOUT", "-a", "STDERR", "--network", "none", - "--user", fmt.Sprintf("%s:%s", u.Uid, u.Gid), + "--user", "1:2", "--security-opt=no-new-privileges", }, containerSpec: runtimeutil.ContainerSpec{ Image: "example.com:version", }, - asCurrentUser: true, + UIDGID: "1:2", }, } @@ -129,7 +125,7 @@ metadata: t.FailNow() } - instance, err := NewContainer(tt.containerSpec, tt.asCurrentUser) + instance, err := NewContainer(tt.containerSpec, tt.UIDGID) if err != nil { t.Fatal(err) } diff --git a/kyaml/runfn/runfn.go b/kyaml/runfn/runfn.go index a7d76dc54..23f416d86 100644 --- a/kyaml/runfn/runfn.go +++ b/kyaml/runfn/runfn.go @@ -7,6 +7,7 @@ import ( "fmt" "io" "os" + "os/user" "path" "path/filepath" "sort" @@ -83,7 +84,7 @@ type RunFns struct { // functionFilterProvider provides a filter to perform the function. // this is a variable so it can be mocked in tests functionFilterProvider func( - filter runtimeutil.FunctionSpec, api *yaml.RNode) (kio.Filter, error) + filter runtimeutil.FunctionSpec, api *yaml.RNode, currentUser currentUserFunc) (kio.Filter, error) // AsCurrentUser is a boolean to indicate whether docker container should use // the uid and gid that run the command @@ -303,7 +304,7 @@ func (r RunFns) getFunctionFilters(global bool, fns ...*yaml.RNode) ( // merge envs from imperative and declarative spec.Container.Env = r.mergeContainerEnv(spec.Container.Env) - c, err := r.functionFilterProvider(*spec, api) + c, err := r.functionFilterProvider(*spec, api, user.Current) if err != nil { return nil, err } @@ -395,8 +396,24 @@ func (r *RunFns) init() { } } +type currentUserFunc func() (*user.User, error) + +// getUIDGID will return "nobody" if asCurrentUser is false. Otherwise +// return "uid:gid" according to the return from currentUser function. +func getUIDGID(asCurrentUser bool, currentUser currentUserFunc) (string, error) { + if !asCurrentUser { + return "nobody", nil + } + + u, err := currentUser() + if err != nil { + return "", err + } + return fmt.Sprintf("%s:%s", u.Uid, u.Gid), nil +} + // ffp provides function filters -func (r *RunFns) ffp(spec runtimeutil.FunctionSpec, api *yaml.RNode) (kio.Filter, error) { +func (r *RunFns) ffp(spec runtimeutil.FunctionSpec, api *yaml.RNode, currentUser currentUserFunc) (kio.Filter, error) { var resultsFile string if r.ResultsDir != "" { resultsFile = filepath.Join(r.ResultsDir, fmt.Sprintf( @@ -405,6 +422,10 @@ func (r *RunFns) ffp(spec runtimeutil.FunctionSpec, api *yaml.RNode) (kio.Filter } if !r.DisableContainers && spec.Container.Image != "" { // TODO: Add a test for this behavior + uidgid, err := getUIDGID(r.AsCurrentUser, currentUser) + if err != nil { + return nil, err + } c, err := container.NewContainer( runtimeutil.ContainerSpec{ Image: spec.Container.Image, @@ -412,7 +433,7 @@ func (r *RunFns) ffp(spec runtimeutil.FunctionSpec, api *yaml.RNode) (kio.Filter StorageMounts: r.StorageMounts, Env: spec.Container.Env, }, - r.AsCurrentUser, + uidgid, ) if err != nil { return nil, err diff --git a/kyaml/runfn/runfn_test.go b/kyaml/runfn/runfn_test.go index 937651744..aaf540421 100644 --- a/kyaml/runfn/runfn_test.go +++ b/kyaml/runfn/runfn_test.go @@ -8,6 +8,7 @@ import ( "fmt" "io/ioutil" "os" + "os/user" "path/filepath" "runtime" "strings" @@ -38,6 +39,13 @@ replace: StatefulSet ` ) +func currentUser() (*user.User, error) { + return &user.User{ + Uid: "1", + Gid: "2", + }, nil +} + func TestRunFns_init(t *testing.T) { instance := RunFns{} instance.init() @@ -59,8 +67,41 @@ kind: if !assert.NoError(t, err) { return } - filter, _ := instance.functionFilterProvider(spec, api) - c, err := container.NewContainer(runtimeutil.ContainerSpec{Image: "example.com:version"}, false) + filter, _ := instance.functionFilterProvider(spec, api, currentUser) + c, err := container.NewContainer(runtimeutil.ContainerSpec{Image: "example.com:version"}, "nobody") + if err != nil { + t.Fatal(err) + } + cf := &c + cf.Exec.FunctionConfig = api + assert.Equal(t, cf, filter) +} + +func TestRunFns_initAsCurrentUser(t *testing.T) { + instance := RunFns{ + AsCurrentUser: true, + } + instance.init() + if !assert.Equal(t, instance.Input, os.Stdin) { + t.FailNow() + } + if !assert.Equal(t, instance.Output, os.Stdout) { + t.FailNow() + } + + api, err := yaml.Parse(`apiVersion: apps/v1 +kind: +`) + spec := runtimeutil.FunctionSpec{ + Container: runtimeutil.ContainerSpec{ + Image: "example.com:version", + }, + } + if !assert.NoError(t, err) { + return + } + filter, _ := instance.functionFilterProvider(spec, api, currentUser) + c, err := container.NewContainer(runtimeutil.ContainerSpec{Image: "example.com:version"}, "1:2") if err != nil { t.Fatal(err) } @@ -93,8 +134,8 @@ kind: if !assert.NoError(t, err) { return } - filter, _ := instance.functionFilterProvider(spec, api) - c, err := container.NewContainer(runtimeutil.ContainerSpec{Image: "example.com:version"}, false) + filter, _ := instance.functionFilterProvider(spec, api, currentUser) + c, err := container.NewContainer(runtimeutil.ContainerSpec{Image: "example.com:version"}, "nobody") if err != nil { t.Fatal(err) } @@ -869,7 +910,7 @@ replace: StatefulSet var fltrs []*TestFilter instance := RunFns{ Path: dir, - functionFilterProvider: func(f runtimeutil.FunctionSpec, node *yaml.RNode) (kio.Filter, error) { + functionFilterProvider: func(f runtimeutil.FunctionSpec, node *yaml.RNode, currentUser currentUserFunc) (kio.Filter, error) { tf := &TestFilter{ Exit: errors.Errorf("message: %s", f.Container.Image), } @@ -1075,8 +1116,8 @@ func setupTest(t *testing.T) string { // getFilterProvider fakes the creation of a filter, replacing the ContainerFiler with // a filter to s/kind: Deployment/kind: StatefulSet/g. // this can be used to simulate running a filter. -func getFilterProvider(t *testing.T) func(runtimeutil.FunctionSpec, *yaml.RNode) (kio.Filter, error) { - return func(f runtimeutil.FunctionSpec, node *yaml.RNode) (kio.Filter, error) { +func getFilterProvider(t *testing.T) func(runtimeutil.FunctionSpec, *yaml.RNode, currentUserFunc) (kio.Filter, error) { + return func(f runtimeutil.FunctionSpec, node *yaml.RNode, currentUser currentUserFunc) (kio.Filter, error) { // parse the filter from the input filter := yaml.YFilter{} b := &bytes.Buffer{}