From 820f446a2b411c7a311ff0cb5e1462eac5158f90 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 | 34 +++++++++++
.../internalversion/zz_generated.deepcopy.go | 31 ++++++++++
pkg/apis/v1alpha1/exec_types.go | 10 ++++
pkg/apis/v1alpha1/zz_generated.deepcopy.go | 31 ++++++++++
pkg/kwok/server/debugging_exec.go | 5 ++
pkg/utils/exec/cmd_other.go | 27 +++++++++
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/fake-deployment.yaml | 3 +
test/kwokctl/kwokctl_exec_test.sh | 4 +-
13 files changed, 236 insertions(+), 1 deletion(-)
diff --git a/pkg/apis/internalversion/exec_types.go b/pkg/apis/internalversion/exec_types.go
index 8fd19f2155..f2c94d5d00 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 specifies the existing uid and gid to run exec command in container process.
+type User struct {
+ // RunAsUser is the existing uid to run exec command in container process.
+ RunAsUser *int64
+ // RunAsGroup is the existing gid to run exec command in container process.
+ RunAsGroup *int64
+}
diff --git a/pkg/apis/internalversion/zz_generated.conversion.go b/pkg/apis/internalversion/zz_generated.conversion.go
index b882eb33ee..420af11638 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,7 @@ 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))
+ out.User = (*v1alpha1.User)(unsafe.Pointer(in.User))
return nil
}
@@ -1047,6 +1058,7 @@ 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))
+ out.User = (*User)(unsafe.Pointer(in.User))
return nil
}
@@ -2058,6 +2070,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.RunAsUser = (*int64)(unsafe.Pointer(in.RunAsUser))
+ out.RunAsGroup = (*int64)(unsafe.Pointer(in.RunAsGroup))
+ 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.RunAsUser = (*int64)(unsafe.Pointer(in.RunAsUser))
+ out.RunAsGroup = (*int64)(unsafe.Pointer(in.RunAsGroup))
+ 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..88210cfcf5 100644
--- a/pkg/apis/internalversion/zz_generated.deepcopy.go
+++ b/pkg/apis/internalversion/zz_generated.deepcopy.go
@@ -446,6 +446,11 @@ func (in *ExecTargetLocal) DeepCopyInto(out *ExecTargetLocal) {
*out = make([]EnvVar, len(*in))
copy(*out, *in)
}
+ if in.User != nil {
+ in, out := &in.User, &out.User
+ *out = new(User)
+ (*in).DeepCopyInto(*out)
+ }
return
}
@@ -1131,6 +1136,32 @@ 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
+ if in.RunAsUser != nil {
+ in, out := &in.RunAsUser, &out.RunAsUser
+ *out = new(int64)
+ **out = **in
+ }
+ if in.RunAsGroup != nil {
+ in, out := &in.RunAsGroup, &out.RunAsGroup
+ *out = new(int64)
+ **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..90d16567f7 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 specifies the existing uid and gid to run exec command in container process.
+type User struct {
+ // RunAsUser is the existing uid to run exec command in container process.
+ RunAsUser *int64 `json:"runAsUser,omitempty"`
+ // RunAsGroup is the existing gid to run exec command in container process.
+ RunAsGroup *int64 `json:"runAsGroup,omitempty"`
+}
diff --git a/pkg/apis/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/v1alpha1/zz_generated.deepcopy.go
index cb50fb7cb1..86e720a444 100644
--- a/pkg/apis/v1alpha1/zz_generated.deepcopy.go
+++ b/pkg/apis/v1alpha1/zz_generated.deepcopy.go
@@ -416,6 +416,11 @@ func (in *ExecTargetLocal) DeepCopyInto(out *ExecTargetLocal) {
*out = make([]EnvVar, len(*in))
copy(*out, *in)
}
+ if in.User != nil {
+ in, out := &in.User, &out.User
+ *out = new(User)
+ (*in).DeepCopyInto(*out)
+ }
return
}
@@ -1000,3 +1005,29 @@ 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
+ if in.RunAsUser != nil {
+ in, out := &in.RunAsUser, &out.RunAsUser
+ *out = new(int64)
+ **out = **in
+ }
+ if in.RunAsGroup != nil {
+ in, out := &in.RunAsGroup, &out.RunAsGroup
+ *out = new(int64)
+ **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..0a18aa50c9 100644
--- a/pkg/kwok/server/debugging_exec.go
+++ b/pkg/kwok/server/debugging_exec.go
@@ -53,6 +53,11 @@ func (s *Server) ExecInContainer(ctx context.Context, podName, podNamespace stri
ctx = exec.WithEnv(ctx, envs)
}
+ // Set the user.
+ if execTarget.Local.User != nil {
+ ctx = exec.WithUser(ctx, execTarget.Local.User.RunAsUser, execTarget.Local.User.RunAsGroup)
+ }
+
// 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..b5f5f6f1bb 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,26 @@ func isRunning(pid int) bool {
err = process.Signal(syscall.Signal(0))
return err == nil
}
+
+func setUser(ctx context.Context, uid, gid *int64, cmd *exec.Cmd) {
+ logger := log.FromContext(ctx)
+
+ u, err := user.LookupId(strconv.Itoa(int(*uid)))
+ if err != nil {
+ logger.Warn("failed to lookup user", "error", err.Error())
+ return
+ }
+ g, err := user.LookupGroupId(strconv.Itoa(int(*gid)))
+ if err != nil {
+ logger.Warn("failed to lookup group", "error", err.Error())
+ return
+ }
+
+ logger.Info("uid", u.Uid, "username", u.Username, "gid", gid, "group name", g.Name)
+ 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..a1e39b7e33 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 setUser(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..0c271e11ac 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
+ // UID is the user id of the command
+ UID *int64
+ // GID is the group id of the command
+ GID *int64
// 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, uid, gid *int64) context.Context {
+ ctx, opt := withExecOptions(ctx)
+ opt.UID = uid
+ opt.GID = gid
+ 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.UID != nil && opt.GID != nil {
+ setUser(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..fc44f392df 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 specifies the existing uid and gid to run exec command in container process.
+
+
+
+
+Field |
+Description |
+
+
+
+
+
+runAsUser
+
+int64
+
+ |
+
+ RunAsUser is the existing uid to run exec command in container process.
+ |
+
+
+
+runAsGroup
+
+int64
+
+ |
+
+ RunAsGroup is the existing gid to run exec command in container process.
+ |
+
+
+
diff --git a/test/kwokctl/exec.yaml b/test/kwokctl/exec.yaml
index c0971adca3..7b05e07e64 100644
--- a/test/kwokctl/exec.yaml
+++ b/test/kwokctl/exec.yaml
@@ -24,3 +24,6 @@ spec:
envs:
- name: TEST_ENV
value: test
+ user:
+ runAsUser: 1001
+ runAsGroup: 1002
diff --git a/test/kwokctl/fake-deployment.yaml b/test/kwokctl/fake-deployment.yaml
index 4c6a5ed587..03e9ed0f9b 100644
--- a/test/kwokctl/fake-deployment.yaml
+++ b/test/kwokctl/fake-deployment.yaml
@@ -13,6 +13,9 @@ spec:
labels:
app: fake-pod
spec:
+ securityContext:
+ runAsUser: 1001
+ runAsGroup: 1002
containers:
- name: fake-pod
image: fake
diff --git a/test/kwokctl/kwokctl_exec_test.sh b/test/kwokctl/kwokctl_exec_test.sh
index a2f432a4c4..612e544213 100755
--- a/test/kwokctl/kwokctl_exec_test.sh
+++ b/test/kwokctl/kwokctl_exec_test.sh
@@ -45,7 +45,7 @@ function test_exec() {
local want="${5}"
local result
for ((i = 0; i < 10; i++)); do
- result=$(kwokctl --name "${name}" kubectl -n "${namespace}" exec -i "${target}" -- "${cmd}" || :)
+ result=$(kwokctl --name "${name}" kubectl -n "${namespace}" exec -i "${target}" -- ${cmd} || :)
if [[ "${result}" == *"${want}"* ]]; then
break
fi
@@ -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 "id -u" "1001" || failed+=("${name}_cluster_default_exec")
+ test_exec "${name}" default deploy/fake-pod "id -g" "1002" || failed+=("${name}_cluster_default_exec")
delete_cluster "${name}"
done