From e97540f1135cace9bbe45dcdbd0f42f742f40d9a Mon Sep 17 00:00:00 2001
From: lianghao208
Date: Sun, 25 Jun 2023 19:34:48 +0800
Subject: [PATCH] specify the user and group to be executed for the exec of
kwokctl
---
pkg/apis/internalversion/exec_types.go | 10 ++++
.../zz_generated.conversion.go | 38 +++++++++++++
.../internalversion/zz_generated.deepcopy.go | 17 ++++++
pkg/apis/v1alpha1/exec_types.go | 10 ++++
pkg/apis/v1alpha1/zz_generated.deepcopy.go | 17 ++++++
pkg/kwok/server/debugging_exec.go | 15 +++++
pkg/utils/exec/cmd_other.go | 47 ++++++++++++++++
pkg/utils/exec/cmd_windows.go | 8 +++
pkg/utils/exec/exec.go | 15 +++++
site/content/en/docs/generated/apis.md | 56 +++++++++++++++++++
test/kwokctl/exec.yaml | 3 +
test/kwokctl/kwokctl_exec_test.sh | 2 +
12 files changed, 238 insertions(+)
diff --git a/pkg/apis/internalversion/exec_types.go b/pkg/apis/internalversion/exec_types.go
index 8fd19f2155..e70d125920 100644
--- a/pkg/apis/internalversion/exec_types.go
+++ b/pkg/apis/internalversion/exec_types.go
@@ -50,6 +50,8 @@ type ExecTargetLocal struct {
WorkDir string
// Envs is a list of environment variables to exec with.
Envs []EnvVar
+ // User is the user to exec.
+ User User
}
// EnvVar represents an environment variable present in a Container.
@@ -59,3 +61,11 @@ type EnvVar struct {
// Value of the environment variable.
Value string
}
+
+// User is the username and group name in a Container.
+type User struct {
+ // Name of the user.
+ Name string
+ // Group of the user.
+ Group string
+}
diff --git a/pkg/apis/internalversion/zz_generated.conversion.go b/pkg/apis/internalversion/zz_generated.conversion.go
index b882eb33ee..6982409de7 100644
--- a/pkg/apis/internalversion/zz_generated.conversion.go
+++ b/pkg/apis/internalversion/zz_generated.conversion.go
@@ -538,6 +538,16 @@ func RegisterConversions(s *runtime.Scheme) error {
}); err != nil {
return err
}
+ if err := s.AddGeneratedConversionFunc((*User)(nil), (*v1alpha1.User)(nil), func(a, b interface{}, scope conversion.Scope) error {
+ return Convert_internalversion_User_To_v1alpha1_User(a.(*User), b.(*v1alpha1.User), scope)
+ }); err != nil {
+ return err
+ }
+ if err := s.AddGeneratedConversionFunc((*v1alpha1.User)(nil), (*User)(nil), func(a, b interface{}, scope conversion.Scope) error {
+ return Convert_v1alpha1_User_To_internalversion_User(a.(*v1alpha1.User), b.(*User), scope)
+ }); err != nil {
+ return err
+ }
if err := s.AddGeneratedConversionFunc((*Volume)(nil), (*configv1alpha1.Volume)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_internalversion_Volume_To_v1alpha1_Volume(a.(*Volume), b.(*configv1alpha1.Volume), scope)
}); err != nil {
@@ -1036,6 +1046,9 @@ func Convert_v1alpha1_ExecTarget_To_internalversion_ExecTarget(in *v1alpha1.Exec
func autoConvert_internalversion_ExecTargetLocal_To_v1alpha1_ExecTargetLocal(in *ExecTargetLocal, out *v1alpha1.ExecTargetLocal, s conversion.Scope) error {
out.WorkDir = in.WorkDir
out.Envs = *(*[]v1alpha1.EnvVar)(unsafe.Pointer(&in.Envs))
+ if err := Convert_internalversion_User_To_v1alpha1_User(&in.User, &out.User, s); err != nil {
+ return err
+ }
return nil
}
@@ -1047,6 +1060,9 @@ func Convert_internalversion_ExecTargetLocal_To_v1alpha1_ExecTargetLocal(in *Exe
func autoConvert_v1alpha1_ExecTargetLocal_To_internalversion_ExecTargetLocal(in *v1alpha1.ExecTargetLocal, out *ExecTargetLocal, s conversion.Scope) error {
out.WorkDir = in.WorkDir
out.Envs = *(*[]EnvVar)(unsafe.Pointer(&in.Envs))
+ if err := Convert_v1alpha1_User_To_internalversion_User(&in.User, &out.User, s); err != nil {
+ return err
+ }
return nil
}
@@ -2058,6 +2074,28 @@ func Convert_v1alpha1_StageSpec_To_internalversion_StageSpec(in *v1alpha1.StageS
return autoConvert_v1alpha1_StageSpec_To_internalversion_StageSpec(in, out, s)
}
+func autoConvert_internalversion_User_To_v1alpha1_User(in *User, out *v1alpha1.User, s conversion.Scope) error {
+ out.Name = in.Name
+ out.Group = in.Group
+ return nil
+}
+
+// Convert_internalversion_User_To_v1alpha1_User is an autogenerated conversion function.
+func Convert_internalversion_User_To_v1alpha1_User(in *User, out *v1alpha1.User, s conversion.Scope) error {
+ return autoConvert_internalversion_User_To_v1alpha1_User(in, out, s)
+}
+
+func autoConvert_v1alpha1_User_To_internalversion_User(in *v1alpha1.User, out *User, s conversion.Scope) error {
+ out.Name = in.Name
+ out.Group = in.Group
+ return nil
+}
+
+// Convert_v1alpha1_User_To_internalversion_User is an autogenerated conversion function.
+func Convert_v1alpha1_User_To_internalversion_User(in *v1alpha1.User, out *User, s conversion.Scope) error {
+ return autoConvert_v1alpha1_User_To_internalversion_User(in, out, s)
+}
+
func autoConvert_internalversion_Volume_To_v1alpha1_Volume(in *Volume, out *configv1alpha1.Volume, s conversion.Scope) error {
out.Name = in.Name
if err := v1.Convert_bool_To_Pointer_bool(&in.ReadOnly, &out.ReadOnly, s); err != nil {
diff --git a/pkg/apis/internalversion/zz_generated.deepcopy.go b/pkg/apis/internalversion/zz_generated.deepcopy.go
index 2e1e9d8eb1..bc2902bf86 100644
--- a/pkg/apis/internalversion/zz_generated.deepcopy.go
+++ b/pkg/apis/internalversion/zz_generated.deepcopy.go
@@ -446,6 +446,7 @@ func (in *ExecTargetLocal) DeepCopyInto(out *ExecTargetLocal) {
*out = make([]EnvVar, len(*in))
copy(*out, *in)
}
+ out.User = in.User
return
}
@@ -1131,6 +1132,22 @@ func (in *StageSpec) DeepCopy() *StageSpec {
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *User) DeepCopyInto(out *User) {
+ *out = *in
+ return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new User.
+func (in *User) DeepCopy() *User {
+ if in == nil {
+ return nil
+ }
+ out := new(User)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Volume) DeepCopyInto(out *Volume) {
*out = *in
diff --git a/pkg/apis/v1alpha1/exec_types.go b/pkg/apis/v1alpha1/exec_types.go
index ba0494e900..1d024522be 100644
--- a/pkg/apis/v1alpha1/exec_types.go
+++ b/pkg/apis/v1alpha1/exec_types.go
@@ -59,6 +59,8 @@ type ExecTargetLocal struct {
WorkDir string `json:"workDir,omitempty"`
// Envs is a list of environment variables to exec with.
Envs []EnvVar `json:"envs,omitempty"`
+ // User is the user to exec.
+ User User `json:"user,omitempty"`
}
// EnvVar represents an environment variable present in a Container.
@@ -68,3 +70,11 @@ type EnvVar struct {
// Value of the environment variable.
Value string `json:"value,omitempty"`
}
+
+// User is the username and group name in a Container.
+type User struct {
+ // Name of the user.
+ Name string `json:"name,omitempty"`
+ // Group of the user.
+ Group string `json:"group,omitempty"`
+}
diff --git a/pkg/apis/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/v1alpha1/zz_generated.deepcopy.go
index cb50fb7cb1..ec3a1f065c 100644
--- a/pkg/apis/v1alpha1/zz_generated.deepcopy.go
+++ b/pkg/apis/v1alpha1/zz_generated.deepcopy.go
@@ -416,6 +416,7 @@ func (in *ExecTargetLocal) DeepCopyInto(out *ExecTargetLocal) {
*out = make([]EnvVar, len(*in))
copy(*out, *in)
}
+ out.User = in.User
return
}
@@ -1000,3 +1001,19 @@ func (in *StageSpec) DeepCopy() *StageSpec {
in.DeepCopyInto(out)
return out
}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *User) DeepCopyInto(out *User) {
+ *out = *in
+ return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new User.
+func (in *User) DeepCopy() *User {
+ if in == nil {
+ return nil
+ }
+ out := new(User)
+ in.DeepCopyInto(out)
+ return out
+}
diff --git a/pkg/kwok/server/debugging_exec.go b/pkg/kwok/server/debugging_exec.go
index 3b7ec6c772..d330f52819 100644
--- a/pkg/kwok/server/debugging_exec.go
+++ b/pkg/kwok/server/debugging_exec.go
@@ -33,6 +33,11 @@ import (
"sigs.k8s.io/kwok/pkg/utils/slices"
)
+const (
+ defaultUsername = "guest"
+ defaultGroupName = "guest"
+)
+
// ExecInContainer executes a command in a container.
func (s *Server) ExecInContainer(ctx context.Context, podName, podNamespace string, uid types.UID, container string, cmd []string, in io.Reader, out, errOut io.WriteCloser, tty bool, resize <-chan clientremotecommand.TerminalSize) error {
execTarget, err := s.getExecTarget(podName, podNamespace, container)
@@ -53,6 +58,16 @@ func (s *Server) ExecInContainer(ctx context.Context, podName, podNamespace stri
ctx = exec.WithEnv(ctx, envs)
}
+ // Set the user.
+ username, groupName := defaultUsername, defaultGroupName
+ if execTarget.Local.User.Name != "" {
+ username = execTarget.Local.User.Name
+ }
+ if execTarget.Local.User.Group != "" {
+ groupName = execTarget.Local.User.Group
+ }
+ ctx = exec.WithUser(ctx, username, groupName)
+
// Set the working directory.
if execTarget.Local.WorkDir != "" {
ctx = exec.WithDir(ctx, execTarget.Local.WorkDir)
diff --git a/pkg/utils/exec/cmd_other.go b/pkg/utils/exec/cmd_other.go
index 575c4dc1c7..b96babb333 100644
--- a/pkg/utils/exec/cmd_other.go
+++ b/pkg/utils/exec/cmd_other.go
@@ -22,7 +22,11 @@ import (
"context"
"os"
"os/exec"
+ "os/user"
+ "strconv"
"syscall"
+
+ "sigs.k8s.io/kwok/pkg/log"
)
func startProcess(ctx context.Context, name string, arg ...string) *exec.Cmd {
@@ -47,3 +51,46 @@ func isRunning(pid int) bool {
err = process.Signal(syscall.Signal(0))
return err == nil
}
+
+func createAndSetUser(ctx context.Context, username, groupName string, cmd *exec.Cmd) {
+ logger := log.FromContext(ctx)
+
+ _, err := exec.Command("groupadd", groupName).Output()
+ if err != nil {
+ logger.Warn("failed to add group", "error", err.Error())
+ }
+
+ _, err = exec.Command("useradd", "-g", username, groupName).Output()
+ if err != nil {
+ logger.Warn("failed to add user", "error", err.Error())
+ }
+
+ u, err := user.Lookup(username)
+ if err != nil {
+ logger.Warn("failed to lookup user", "error", err.Error())
+ return
+ }
+ g, err := user.LookupGroup(groupName)
+ if err != nil {
+ logger.Warn("failed to lookup group", "error", err.Error())
+ return
+ }
+
+ uid, err := strconv.Atoi(u.Uid)
+ if err != nil {
+ logger.Warn("failed to convert uid to integers", "error", err.Error())
+ return
+ }
+ gid, err := strconv.Atoi(g.Gid)
+ if err != nil {
+ logger.Warn("failed to convert gid to integers", "error", err.Error())
+ return
+ }
+ logger.Info("username", username, "group name", groupName)
+ cmd.SysProcAttr = &syscall.SysProcAttr{
+ Credential: &syscall.Credential{
+ Uid: uint32(uid),
+ Gid: uint32(gid),
+ },
+ }
+}
diff --git a/pkg/utils/exec/cmd_windows.go b/pkg/utils/exec/cmd_windows.go
index 756d2efb1d..ed3c49dfba 100644
--- a/pkg/utils/exec/cmd_windows.go
+++ b/pkg/utils/exec/cmd_windows.go
@@ -25,6 +25,8 @@ import (
"syscall"
"golang.org/x/sys/windows"
+
+ "sigs.k8s.io/kwok/pkg/log"
)
func startProcess(ctx context.Context, name string, arg ...string) *exec.Cmd {
@@ -47,3 +49,9 @@ func isRunning(pid int) bool {
_, err := os.FindProcess(pid)
return err == nil
}
+
+func createAndSetUser(ctx context.Context, username, groupName string, cmd *exec.Cmd) {
+ logger := log.FromContext(ctx)
+ err := "user and group are not supported in windows, ignoring the custom user and group"
+ logger.Warn(err)
+}
diff --git a/pkg/utils/exec/exec.go b/pkg/utils/exec/exec.go
index b2816e710b..531dbad788 100644
--- a/pkg/utils/exec/exec.go
+++ b/pkg/utils/exec/exec.go
@@ -45,6 +45,10 @@ type execOptions struct {
Dir string
// Env is the environment variables of the command.
Env []string
+ // Username is the username of the command
+ Username string
+ // GroupName is the group name of the command
+ GroupName string
// IOStreams contains the standard streams.
IOStreams
// PipeStdin is true if the command's stdin should be piped.
@@ -65,6 +69,14 @@ func WithEnv(ctx context.Context, env []string) context.Context {
return ctx
}
+// WithUser returns a context with the given username and group name.
+func WithUser(ctx context.Context, username, groupName string) context.Context {
+ ctx, opt := withExecOptions(ctx)
+ opt.Username = username
+ opt.GroupName = groupName
+ return ctx
+}
+
// WithDir returns a context with the given working directory.
func WithDir(ctx context.Context, dir string) context.Context {
ctx, opt := withExecOptions(ctx)
@@ -141,6 +153,9 @@ func Exec(ctx context.Context, name string, arg ...string) error {
cmd.Env = opt.Env
cmd.Env = append(os.Environ(), cmd.Env...)
}
+ if opt.Username != "" && opt.GroupName != "" {
+ createAndSetUser(ctx, opt.Username, opt.GroupName, cmd)
+ }
cmd.Dir = opt.Dir
if opt.In != nil {
if opt.PipeStdin {
diff --git a/site/content/en/docs/generated/apis.md b/site/content/en/docs/generated/apis.md
index 6bde6982d7..10fde16cc9 100644
--- a/site/content/en/docs/generated/apis.md
+++ b/site/content/en/docs/generated/apis.md
@@ -3219,6 +3219,19 @@ string
Envs is a list of environment variables to exec with.
+
+
+user
+
+
+User
+
+
+ |
+
+ User is the user to exec.
+ |
+
@@ -4355,3 +4368,46 @@ bool
+
+User
+ #
+
+
+Appears on:
+ExecTargetLocal
+
+
+
User is the username and group name in a Container.
+
+
+
+
+Field |
+Description |
+
+
+
+
+
+name
+
+string
+
+ |
+
+ Name of the user.
+ |
+
+
+
+group
+
+string
+
+ |
+
+ Group of the user.
+ |
+
+
+
diff --git a/test/kwokctl/exec.yaml b/test/kwokctl/exec.yaml
index c0971adca3..bcc2c4bf54 100644
--- a/test/kwokctl/exec.yaml
+++ b/test/kwokctl/exec.yaml
@@ -24,3 +24,6 @@ spec:
envs:
- name: TEST_ENV
value: test
+ user:
+ name: test
+ group: test
diff --git a/test/kwokctl/kwokctl_exec_test.sh b/test/kwokctl/kwokctl_exec_test.sh
index a2f432a4c4..62e7400b8a 100755
--- a/test/kwokctl/kwokctl_exec_test.sh
+++ b/test/kwokctl/kwokctl_exec_test.sh
@@ -92,6 +92,8 @@ function main() {
test_apply_node_and_pod "${name}" || failed+=("apply_node_and_pod")
test_exec "${name}" other pod/fake-pod "pwd" "/tmp" || failed+=("${name}_target_exec")
test_exec "${name}" default deploy/fake-pod "env" "TEST_ENV=test" || failed+=("${name}_cluster_default_exec")
+ test_exec "${name}" default deploy/fake-pod "echo $USER" "test" || failed+=("${name}_cluster_default_exec")
+ test_exec "${name}" default deploy/fake-pod "getent group $GROUPS | cut -d: -f1" "test" || failed+=("${name}_cluster_default_exec")
delete_cluster "${name}"
done