diff --git a/components/execd/pkg/runtime/command.go b/components/execd/pkg/runtime/command.go index 11f2cf09..c300a97a 100644 --- a/components/execd/pkg/runtime/command.go +++ b/components/execd/pkg/runtime/command.go @@ -24,6 +24,7 @@ import ( "os" "os/exec" "os/signal" + "os/user" "strconv" "sync" "syscall" @@ -34,6 +35,46 @@ import ( "github.com/alibaba/opensandbox/execd/pkg/util/safego" ) +func buildCredential(uid, gid *uint32) (*syscall.Credential, error) { + if uid == nil && gid == nil { + return nil, nil + } + + cred := &syscall.Credential{} + if uid != nil { + cred.Uid = *uid + // Load user info to get primary GID and supplemental groups + u, err := user.LookupId(strconv.FormatUint(uint64(*uid), 10)) + if err == nil { + // Set primary GID if not explicitly provided + if gid == nil { + primaryGid, err := strconv.ParseUint(u.Gid, 10, 32) + if err == nil { + cred.Gid = uint32(primaryGid) + } + } + + // Load supplemental groups + gids, err := u.GroupIds() + if err == nil { + for _, g := range gids { + id, err := strconv.ParseUint(g, 10, 32) + if err == nil { + cred.Groups = append(cred.Groups, uint32(id)) + } + } + } + } + } + + // Override Gid if explicitly provided + if gid != nil { + cred.Gid = *gid + } + + return cred, nil +} + // runCommand executes shell commands and streams their output. func (c *Controller) runCommand(ctx context.Context, request *ExecuteCodeRequest) error { session := c.newContextID() @@ -71,10 +112,19 @@ func (c *Controller) runCommand(ctx context.Context, request *ExecuteCodeRequest }) cmd.Dir = request.Cwd - // use a dedicated process group so signals propagate to children. - cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} + + // Configure credentials and process group + cred, err := buildCredential(request.Uid, request.Gid) + if err != nil { + log.Error("failed to build credentials: %v", err) + } + cmd.SysProcAttr = &syscall.SysProcAttr{ + Setpgid: true, + Credential: cred, + } err = cmd.Start() + if err != nil { request.Hooks.OnExecuteInit(session) request.Hooks.OnExecuteError(&execute.ErrorOutput{EName: "CommandExecError", EValue: err.Error()}) @@ -171,8 +221,19 @@ func (c *Controller) runBackgroundCommand(ctx context.Context, cancel context.Ca cmd := exec.CommandContext(ctx, "bash", "-c", request.Code) cmd.Dir = request.Cwd - cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} + + // Configure credentials and process group + cred, err := buildCredential(request.Uid, request.Gid) + if err != nil { + log.Error("failed to build credentials: %v", err) + } + cmd.SysProcAttr = &syscall.SysProcAttr{ + Setpgid: true, + Credential: cred, + } + cmd.Stdout = pipe + cmd.Stderr = pipe cmd.Env = mergeEnvs(os.Environ(), loadExtraEnvFromFile()) diff --git a/components/execd/pkg/runtime/types.go b/components/execd/pkg/runtime/types.go index cb82a11b..b4322f8d 100644 --- a/components/execd/pkg/runtime/types.go +++ b/components/execd/pkg/runtime/types.go @@ -34,15 +34,16 @@ type ExecuteResultHook struct { // ExecuteCodeRequest represents a code execution request with context and hooks. type ExecuteCodeRequest struct { - Language Language `json:"language"` - Code string `json:"code"` - Context string `json:"context"` - Timeout time.Duration `json:"timeout"` - Cwd string `json:"cwd"` - Envs map[string]string `json:"envs"` - Hooks ExecuteResultHook + Language Language `json:"language"` + Code string `json:"code"` + Context string `json:"context"` + Timeout time.Duration `json:"timeout"` + Cwd string `json:"cwd"` + Envs map[string]string `json:"envs"` + Uid *uint32 `json:"uid,omitempty"` + Gid *uint32 `json:"gid,omitempty"` + Hooks ExecuteResultHook } - // SetDefaultHooks installs stdout logging fallbacks for unset hooks. func (req *ExecuteCodeRequest) SetDefaultHooks() { if req.Hooks.OnExecuteResult == nil {