add as-current-user for fn run

This commit is contained in:
Donny Xia
2020-09-15 17:08:48 -07:00
parent cb7974cf45
commit 11049fa0bb
5 changed files with 87 additions and 76 deletions

View File

@@ -5,6 +5,7 @@ package container
import ( import (
"fmt" "fmt"
"os/user"
runtimeexec "sigs.k8s.io/kustomize/kyaml/fn/runtime/exec" runtimeexec "sigs.k8s.io/kustomize/kyaml/fn/runtime/exec"
"sigs.k8s.io/kustomize/kyaml/fn/runtime/runtimeutil" "sigs.k8s.io/kustomize/kyaml/fn/runtime/runtimeutil"
@@ -124,6 +125,8 @@ type Filter struct {
runtimeutil.ContainerSpec `json:",inline" yaml:",inline"` runtimeutil.ContainerSpec `json:",inline" yaml:",inline"`
Exec runtimeexec.Filter Exec runtimeexec.Filter
UIDGID string
} }
func (c Filter) String() string { func (c Filter) String() string {
@@ -167,7 +170,7 @@ func (c *Filter) getCommand() (string, []string) {
"--network", string(network), "--network", string(network),
// added security options // added security options
"--user", c.User.String(), "--user", c.UIDGID,
"--security-opt=no-new-privileges", // don't allow the user to escalate privileges "--security-opt=no-new-privileges", // don't allow the user to escalate privileges
// note: don't make fs readonly because things like heredoc rely on writing tmp files // note: don't make fs readonly because things like heredoc rely on writing tmp files
} }
@@ -182,13 +185,28 @@ func (c *Filter) getCommand() (string, []string) {
return "docker", a return "docker", a
} }
// NewContainer returns a new container filter // getUIDGID will return "nobody" if asCurrentUser is false. Otherwise
func NewContainer(spec runtimeutil.ContainerSpec) Filter { // return "uid:gid" according to current user who runs the command.
f := Filter{ContainerSpec: spec} func getUIDGID(asCurrentUser bool) (string, error) {
// default user is nobody if !asCurrentUser {
if f.ContainerSpec.User.IsEmpty() { return "nobody", nil
f.ContainerSpec.User = runtimeutil.UserNobody
} }
return f 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
return f, nil
} }

View File

@@ -6,6 +6,7 @@ package container
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"os/user"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -15,11 +16,16 @@ import (
) )
func TestFilter_setupExec(t *testing.T) { func TestFilter_setupExec(t *testing.T) {
u, err := user.Current()
if err != nil {
t.Fatal(err)
}
var tests = []struct { var tests = []struct {
name string name string
functionConfig string functionConfig string
expectedArgs []string expectedArgs []string
instance Filter containerSpec runtimeutil.ContainerSpec
asCurrentUser bool
}{ }{
{ {
name: "command", name: "command",
@@ -36,12 +42,9 @@ metadata:
"--user", "nobody", "--user", "nobody",
"--security-opt=no-new-privileges", "--security-opt=no-new-privileges",
}, },
instance: NewContainer( containerSpec: runtimeutil.ContainerSpec{
runtimeutil.ContainerSpec{
Image: "example.com:version", Image: "example.com:version",
User: "nobody",
}, },
),
}, },
{ {
@@ -63,9 +66,8 @@ metadata:
runtimeutil.ContainerSpec{ runtimeutil.ContainerSpec{
Image: "example.com:version", Image: "example.com:version",
Network: true, Network: true,
User: "nobody",
}, },
), },
}, },
{ {
@@ -87,8 +89,7 @@ metadata:
"--mount", fmt.Sprintf("type=%s,source=%s,target=%s,readonly", "volume", "myvol", "/local/"), "--mount", fmt.Sprintf("type=%s,source=%s,target=%s,readonly", "volume", "myvol", "/local/"),
"--mount", fmt.Sprintf("type=%s,source=%s,target=%s,readonly", "tmpfs", "", "/local/"), "--mount", fmt.Sprintf("type=%s,source=%s,target=%s,readonly", "tmpfs", "", "/local/"),
}, },
instance: NewContainer( containerSpec: runtimeutil.ContainerSpec{
runtimeutil.ContainerSpec{
Image: "example.com:version", Image: "example.com:version",
StorageMounts: []runtimeutil.StorageMount{ StorageMounts: []runtimeutil.StorageMount{
{MountType: "bind", Src: "/mount/path", DstPath: "/local/"}, {MountType: "bind", Src: "/mount/path", DstPath: "/local/"},
@@ -96,12 +97,10 @@ metadata:
{MountType: "volume", Src: "myvol", DstPath: "/local/"}, {MountType: "volume", Src: "myvol", DstPath: "/local/"},
{MountType: "tmpfs", Src: "", DstPath: "/local/"}, {MountType: "tmpfs", Src: "", DstPath: "/local/"},
}, },
User: "nobody",
}, },
),
}, },
{ {
name: "root user", name: "as current user",
functionConfig: `apiVersion: apps/v1 functionConfig: `apiVersion: apps/v1
kind: Deployment kind: Deployment
metadata: metadata:
@@ -112,15 +111,13 @@ metadata:
"--rm", "--rm",
"-i", "-a", "STDIN", "-a", "STDOUT", "-a", "STDERR", "-i", "-a", "STDIN", "-a", "STDOUT", "-a", "STDERR",
"--network", "none", "--network", "none",
"--user", "root", "--user", fmt.Sprintf("%s:%s", u.Uid, u.Gid),
"--security-opt=no-new-privileges", "--security-opt=no-new-privileges",
}, },
instance: NewContainer( containerSpec: runtimeutil.ContainerSpec{
runtimeutil.ContainerSpec{
Image: "example.com:version", Image: "example.com:version",
User: "root",
}, },
), asCurrentUser: true,
}, },
} }
@@ -131,18 +128,23 @@ metadata:
if !assert.NoError(t, err) { if !assert.NoError(t, err) {
t.FailNow() t.FailNow()
} }
tt.instance.Exec.FunctionConfig = cfg
tt.instance.Env = append(tt.instance.Env, "KYAML_TEST=FOO") instance, err := NewContainer(tt.containerSpec, tt.asCurrentUser)
tt.instance.setupExec() if err != nil {
t.Fatal(err)
}
instance.Exec.FunctionConfig = cfg
instance.Env = append(instance.Env, "KYAML_TEST=FOO")
instance.setupExec()
tt.expectedArgs = append(tt.expectedArgs, tt.expectedArgs = append(tt.expectedArgs,
runtimeutil.NewContainerEnvFromStringSlice(tt.instance.Env).GetDockerFlags()...) runtimeutil.NewContainerEnvFromStringSlice(instance.Env).GetDockerFlags()...)
tt.expectedArgs = append(tt.expectedArgs, tt.instance.Image) tt.expectedArgs = append(tt.expectedArgs, instance.Image)
if !assert.Equal(t, "docker", tt.instance.Exec.Path) { if !assert.Equal(t, "docker", instance.Exec.Path) {
t.FailNow() t.FailNow()
} }
if !assert.Equal(t, tt.expectedArgs, tt.instance.Exec.Args) { if !assert.Equal(t, tt.expectedArgs, instance.Exec.Args) {
t.FailNow() t.FailNow()
} }
}) })

View File

@@ -19,21 +19,6 @@ const (
var functionAnnotationKeys = []string{FunctionAnnotationKey, oldFunctionAnnotationKey} var functionAnnotationKeys = []string{FunctionAnnotationKey, oldFunctionAnnotationKey}
// ContainerUser is a type for username/uid used in container
type ContainerUser string
func (u *ContainerUser) String() string {
return string(*u)
}
func (u *ContainerUser) IsEmpty() bool {
return string(*u) == ""
}
const (
UserNobody ContainerUser = "nobody"
)
// ContainerNetworkName is a type for network name used in container // ContainerNetworkName is a type for network name used in container
type ContainerNetworkName string type ContainerNetworkName string
@@ -171,9 +156,6 @@ type ContainerSpec struct {
// Mounts are the storage or directories to mount into the container // Mounts are the storage or directories to mount into the container
StorageMounts []StorageMount `json:"mounts,omitempty" yaml:"mounts,omitempty"` StorageMounts []StorageMount `json:"mounts,omitempty" yaml:"mounts,omitempty"`
// User is the username/uid that application runs as in continer
User ContainerUser `json:"user,omitempty" yaml:"user,omitempty"`
// Env is a slice of env string that will be exposed to container // Env is a slice of env string that will be exposed to container
Env []string `json:"envs,omitempty" yaml:"envs,omitempty"` Env []string `json:"envs,omitempty" yaml:"envs,omitempty"`
} }

View File

@@ -85,8 +85,9 @@ type RunFns struct {
functionFilterProvider func( functionFilterProvider func(
filter runtimeutil.FunctionSpec, api *yaml.RNode) (kio.Filter, error) filter runtimeutil.FunctionSpec, api *yaml.RNode) (kio.Filter, error)
// User username used to run the application in container, // AsCurrentUser is a boolean to indicate whether docker container should use
User runtimeutil.ContainerUser // the uid and gid that run the command
AsCurrentUser bool
// Env contains environment variables that will be exported to container // Env contains environment variables that will be exported to container
Env []string Env []string
@@ -299,10 +300,7 @@ func (r RunFns) getFunctionFilters(global bool, fns ...*yaml.RNode) (
// TODO(eddiezane): Provide error info about which function needs the network // TODO(eddiezane): Provide error info about which function needs the network
return fltrs, errors.Errorf("network required but not enabled with --network") return fltrs, errors.Errorf("network required but not enabled with --network")
} }
// command line username and envs has higher priority // merge envs from imperative and declarative
if !r.User.IsEmpty() {
spec.Container.User = r.User
}
spec.Container.Env = r.mergeContainerEnv(spec.Container.Env) spec.Container.Env = r.mergeContainerEnv(spec.Container.Env)
c, err := r.functionFilterProvider(*spec, api) c, err := r.functionFilterProvider(*spec, api)
@@ -407,13 +405,18 @@ func (r *RunFns) ffp(spec runtimeutil.FunctionSpec, api *yaml.RNode) (kio.Filter
} }
if !r.DisableContainers && spec.Container.Image != "" { if !r.DisableContainers && spec.Container.Image != "" {
// TODO: Add a test for this behavior // TODO: Add a test for this behavior
c := container.NewContainer(runtimeutil.ContainerSpec{ c, err := container.NewContainer(
runtimeutil.ContainerSpec{
Image: spec.Container.Image, Image: spec.Container.Image,
Network: spec.Container.Network, Network: spec.Container.Network,
StorageMounts: r.StorageMounts, StorageMounts: r.StorageMounts,
User: spec.Container.User,
Env: spec.Container.Env, Env: spec.Container.Env,
}) },
r.AsCurrentUser,
)
if err != nil {
return nil, err
}
cf := &c cf := &c
cf.Exec.FunctionConfig = api cf.Exec.FunctionConfig = api
cf.Exec.GlobalScope = r.GlobalScope cf.Exec.GlobalScope = r.GlobalScope

View File

@@ -60,7 +60,10 @@ kind:
return return
} }
filter, _ := instance.functionFilterProvider(spec, api) filter, _ := instance.functionFilterProvider(spec, api)
c := container.NewContainer(runtimeutil.ContainerSpec{Image: "example.com:version"}) c, err := container.NewContainer(runtimeutil.ContainerSpec{Image: "example.com:version"}, false)
if err != nil {
t.Fatal(err)
}
cf := &c cf := &c
cf.Exec.FunctionConfig = api cf.Exec.FunctionConfig = api
assert.Equal(t, cf, filter) assert.Equal(t, cf, filter)
@@ -91,7 +94,10 @@ kind:
return return
} }
filter, _ := instance.functionFilterProvider(spec, api) filter, _ := instance.functionFilterProvider(spec, api)
c := container.NewContainer(runtimeutil.ContainerSpec{Image: "example.com:version"}) c, err := container.NewContainer(runtimeutil.ContainerSpec{Image: "example.com:version"}, false)
if err != nil {
t.Fatal(err)
}
cf := &c cf := &c
cf.Exec.FunctionConfig = api cf.Exec.FunctionConfig = api
cf.Exec.GlobalScope = true cf.Exec.GlobalScope = true