From 5c836f4285693f84d15f5cdbdbbeb3867f4ad735 Mon Sep 17 00:00:00 2001 From: Thomas Schubart Date: Thu, 5 Oct 2023 12:41:26 +0000 Subject: [PATCH] Revert "Revert "Relax assumptions in supervisor (#18782)" (#18857)" This reverts commit 40c39f531212176475985a54d972d20768b5d5c7. --- .../content-service/pkg/executor/executor.go | 4 +- components/content-service/pkg/git/git.go | 25 ++-- .../content-service/pkg/initializer/git.go | 4 +- .../pkg/initializer/initializer.go | 25 ++-- components/supervisor/cmd/dump-initializer.go | 38 +++++ .../supervisor/pkg/supervisor/config.go | 32 ++++- .../supervisor/pkg/supervisor/docker.go | 10 +- components/supervisor/pkg/supervisor/git.go | 2 +- .../supervisor/pkg/supervisor/services.go | 20 +-- components/supervisor/pkg/supervisor/ssh.go | 10 +- .../supervisor/pkg/supervisor/supervisor.go | 133 ++++++++++-------- components/supervisor/pkg/supervisor/user.go | 25 ++-- .../supervisor/pkg/supervisor/user_test.go | 18 +-- .../ws-daemon/pkg/content/initializer.go | 2 +- 14 files changed, 230 insertions(+), 118 deletions(-) create mode 100644 components/supervisor/cmd/dump-initializer.go diff --git a/components/content-service/pkg/executor/executor.go b/components/content-service/pkg/executor/executor.go index 66a7fd3d1ba080..f04b5fcbeb3b0b 100644 --- a/components/content-service/pkg/executor/executor.go +++ b/components/content-service/pkg/executor/executor.go @@ -44,7 +44,7 @@ func Prepare(req *csapi.WorkspaceInitializer, urls map[string]string) ([]byte, e // Execute runs an initializer to place content in destination based on the configuration read // from the cfgin stream. -func Execute(ctx context.Context, destination string, cfgin io.Reader, forceGitUser bool, opts ...initializer.InitializeOpt) (src csapi.WorkspaceInitSource, err error) { +func Execute(ctx context.Context, destination string, cfgin io.Reader, runAs *initializer.User, opts ...initializer.InitializeOpt) (src csapi.WorkspaceInitSource, err error) { var cfg config err = json.NewDecoder(cfgin).Decode(&cfg) if err != nil { @@ -64,7 +64,7 @@ func Execute(ctx context.Context, destination string, cfgin io.Reader, forceGitU rs = &storage.NamedURLDownloader{URLs: cfg.URLs} ilr, err = initializer.NewFromRequest(ctx, destination, rs, &req, initializer.NewFromRequestOpts{ - ForceGitpodUserForGit: forceGitUser, + RunAs: runAs, }) if err != nil { return "", err diff --git a/components/content-service/pkg/git/git.go b/components/content-service/pkg/git/git.go index 3a74d5a5778155..a1970331a7f3d4 100644 --- a/components/content-service/pkg/git/git.go +++ b/components/content-service/pkg/git/git.go @@ -13,6 +13,7 @@ import ( "os/exec" "path/filepath" "strings" + "syscall" "github.com/opentracing/opentracing-go" "golang.org/x/xerrors" @@ -93,8 +94,12 @@ type Client struct { // UpstreamCloneURI is the fork upstream of a repository UpstreamRemoteURI string - // if true will run git command as gitpod user (should be executed as root that has access to sudo in this case) - RunAsGitpodUser bool + // RunAs runs the Git commands as a particular user - if not nil + RunAs *User +} + +type User struct { + UID, GID uint32 } // Status describes the status of a Git repo/working copy akin to "git status" @@ -178,8 +183,6 @@ func (c *Client) GitWithOutput(ctx context.Context, ignoreErr *string, subcomman env = append(env, fmt.Sprintf("GIT_AUTH_PASSWORD=%s", pwd)) } - env = append(env, "HOME=/home/gitpod") - fullArgs = append(fullArgs, subcommand) fullArgs = append(fullArgs, args...) @@ -201,13 +204,19 @@ func (c *Client) GitWithOutput(ctx context.Context, ignoreErr *string, subcomman span.LogKV("args", fullArgs) cmdName := "git" - if c.RunAsGitpodUser { - cmdName = "sudo" - fullArgs = append([]string{"-u", "gitpod", "git"}, fullArgs...) - } cmd := exec.Command(cmdName, fullArgs...) cmd.Dir = c.Location cmd.Env = env + if c.RunAs != nil { + if cmd.SysProcAttr == nil { + cmd.SysProcAttr = &syscall.SysProcAttr{} + } + if cmd.SysProcAttr.Credential == nil { + cmd.SysProcAttr.Credential = &syscall.Credential{} + } + cmd.SysProcAttr.Credential.Uid = c.RunAs.UID + cmd.SysProcAttr.Credential.Gid = c.RunAs.UID + } res, err := cmd.CombinedOutput() if err != nil { diff --git a/components/content-service/pkg/initializer/git.go b/components/content-service/pkg/initializer/git.go index f96e81375aec84..e372f0296f91cc 100644 --- a/components/content-service/pkg/initializer/git.go +++ b/components/content-service/pkg/initializer/git.go @@ -95,8 +95,8 @@ func (ws *GitInitializer) Run(ctx context.Context, mappings []archive.IDMapping) // make sure that folder itself is owned by gitpod user prior to doing git clone // this is needed as otherwise git clone will fail if the folder is owned by root - if ws.RunAsGitpodUser { - args := []string{"gitpod", ws.Location} + if ws.RunAs != nil { + args := []string{fmt.Sprintf("%d:%d", ws.RunAs.UID, ws.RunAs.GID), ws.Location} cmd := exec.Command("chown", args...) res, cerr := cmd.CombinedOutput() if cerr != nil && !process.IsNotChildProcess(cerr) { diff --git a/components/content-service/pkg/initializer/initializer.go b/components/content-service/pkg/initializer/initializer.go index d75cb67de1aab4..8ee0d0f1f0f460 100644 --- a/components/content-service/pkg/initializer/initializer.go +++ b/components/content-service/pkg/initializer/initializer.go @@ -99,11 +99,13 @@ func (e CompositeInitializer) Run(ctx context.Context, mappings []archive.IDMapp // NewFromRequestOpts configures the initializer produced from a content init request type NewFromRequestOpts struct { - // ForceGitpodUserForGit forces gitpod:gitpod ownership on all files produced by the Git initializer. - // For FWB workspaces the content init is run from supervisor which runs as UID 0. Using this flag, the - // Git content is forced to the Gitpod user. All other content (backup, prebuild, snapshot) will already - // have the correct user. - ForceGitpodUserForGit bool + // RunAs - if not nil - decides the user with which the initiallisation will be executed + RunAs *User +} + +type User struct { + UID uint32 + GID uint32 } // NewFromRequest picks the initializer from the request but does not execute it. @@ -132,7 +134,7 @@ func NewFromRequest(ctx context.Context, loc string, rs storage.DirectDownloader return nil, status.Error(codes.InvalidArgument, "missing Git initializer spec") } - initializer, err = newGitInitializer(ctx, loc, ir.Git, opts.ForceGitpodUserForGit) + initializer, err = newGitInitializer(ctx, loc, ir.Git, opts.RunAs) } else if ir, ok := spec.(*csapi.WorkspaceInitializer_Prebuild); ok { if ir.Prebuild == nil { return nil, status.Error(codes.InvalidArgument, "missing prebuild initializer spec") @@ -146,7 +148,7 @@ func NewFromRequest(ctx context.Context, loc string, rs storage.DirectDownloader } var gits []*GitInitializer for _, gi := range ir.Prebuild.Git { - gitinit, err := newGitInitializer(ctx, loc, gi, opts.ForceGitpodUserForGit) + gitinit, err := newGitInitializer(ctx, loc, gi, opts.RunAs) if err != nil { return nil, err } @@ -249,7 +251,7 @@ func (bi *fromBackupInitializer) Run(ctx context.Context, mappings []archive.IDM // newGitInitializer creates a Git initializer based on the request. // Returns gRPC errors. -func newGitInitializer(ctx context.Context, loc string, req *csapi.GitInitializer, forceGitpodUser bool) (*GitInitializer, error) { +func newGitInitializer(ctx context.Context, loc string, req *csapi.GitInitializer, runAs *User) (*GitInitializer, error) { if req.Config == nil { return nil, status.Error(codes.InvalidArgument, "Git initializer misses config") } @@ -294,6 +296,11 @@ func newGitInitializer(ctx context.Context, loc string, req *csapi.GitInitialize return }) + var user *git.User + if runAs != nil { + user = &git.User{UID: runAs.UID, GID: runAs.GID} + } + log.WithField("location", loc).Debug("using Git initializer") return &GitInitializer{ Client: git.Client{ @@ -303,7 +310,7 @@ func newGitInitializer(ctx context.Context, loc string, req *csapi.GitInitialize Config: req.Config.CustomConfig, AuthMethod: authMethod, AuthProvider: authProvider, - RunAsGitpodUser: forceGitpodUser, + RunAs: user, }, TargetMode: targetMode, CloneTarget: req.CloneTaget, diff --git a/components/supervisor/cmd/dump-initializer.go b/components/supervisor/cmd/dump-initializer.go new file mode 100644 index 00000000000000..f5c61177c53c1a --- /dev/null +++ b/components/supervisor/cmd/dump-initializer.go @@ -0,0 +1,38 @@ +// Copyright (c) 2021 Gitpod GmbH. All rights reserved. +// Licensed under the GNU Affero General Public License (AGPL). +// See License.AGPL.txt in the project root for license information. + +package cmd + +import ( + "fmt" + + csapi "github.com/gitpod-io/gitpod/content-service/api" + "github.com/gitpod-io/gitpod/content-service/pkg/executor" + "github.com/spf13/cobra" +) + +var dumpInitializer = &cobra.Command{ + Use: "dump-init", + Hidden: true, // this is not official user-facing functionality, but just for debugging + Run: func(cmd *cobra.Command, args []string) { + fc, _ := executor.Prepare(&csapi.WorkspaceInitializer{ + Spec: &csapi.WorkspaceInitializer_Git{ + Git: &csapi.GitInitializer{ + RemoteUri: "https://github.com/gitpod-io/gitpod", + CheckoutLocation: "gitpod", + TargetMode: csapi.CloneTargetMode_REMOTE_BRANCH, + CloneTaget: "main", + Config: &csapi.GitConfig{ + Authentication: csapi.GitAuthMethod_NO_AUTH, + }, + }, + }, + }, nil) + fmt.Println(string(fc)) + }, +} + +func init() { + rootCmd.AddCommand(dumpInitializer) +} diff --git a/components/supervisor/pkg/supervisor/config.go b/components/supervisor/pkg/supervisor/config.go index 94c0ecbc771208..c676b9641ea4fb 100644 --- a/components/supervisor/pkg/supervisor/config.go +++ b/components/supervisor/pkg/supervisor/config.go @@ -8,7 +8,6 @@ import ( "encoding/json" "fmt" "io" - "io/ioutil" "math" "net/http" "net/url" @@ -329,6 +328,35 @@ type WorkspaceConfig struct { // ConfigcatEnabled controls whether configcat is enabled ConfigcatEnabled bool `env:"GITPOD_CONFIGCAT_ENABLED"` + + WorkspaceLinuxUID uint32 `env:"GITPOD_WORKSPACE_LINUX_UID,default=33333"` + WorkspaceLinuxGID uint32 `env:"GITPOD_WORKSPACE_LINUX_GID,default=33333"` + + // ContentInitializer - if set - will run the content initializer instead of waiting for the ready file + ContentInitializer string `env:"SUPERVISOR_CONTENT_INITIALIZER"` + + // WorkspaceRuntime configures the runtime supervisor is running in + WorkspaceRuntime WorkspaceRuntime `env:"SUPERVISOR_WORKSPACE_RUNTIME,default=container"` +} + +type WorkspaceRuntime string + +const ( + WorkspaceRuntimeContainer WorkspaceRuntime = "container" + WorkspaceRuntimeNextgen WorkspaceRuntime = "nextgen" + WorkspaceRuntimeRunGP WorkspaceRuntime = "rungp" +) + +func (rt *WorkspaceRuntime) UnmarshalEnvironmentValue(data string) error { + switch WorkspaceRuntime(data) { + case WorkspaceRuntimeContainer, WorkspaceRuntimeNextgen, WorkspaceRuntimeRunGP: + // everything's fine + default: + return fmt.Errorf("unknown workspace runtime: %s", data) + } + + *rt = WorkspaceRuntime(data) + return nil } // WorkspaceGitpodToken is a list of tokens that should be added to supervisor's token service. @@ -587,7 +615,7 @@ func loadDesktopIDEs(static *StaticConfig) ([]*IDEConfig, error) { uniqueDesktopIDEs[desktopIDE.Name] = struct{}{} } - files, err := ioutil.ReadDir(static.DesktopIDERoot) + files, err := os.ReadDir(static.DesktopIDERoot) if err != nil { return nil, err } diff --git a/components/supervisor/pkg/supervisor/docker.go b/components/supervisor/pkg/supervisor/docker.go index 4b25873ca66a8f..a3650de79e2802 100644 --- a/components/supervisor/pkg/supervisor/docker.go +++ b/components/supervisor/pkg/supervisor/docker.go @@ -57,7 +57,7 @@ func socketActivationForDocker(parentCtx context.Context, wg *sync.WaitGroup, te return } - logFile, err := openDockerUpLogFile() + logFile, err := openDockerUpLogFile(int(cfg.WorkspaceLinuxUID), int(cfg.WorkspaceLinuxGID)) if err != nil { log.WithError(err).Error("docker-up: cannot open log file") } else { @@ -164,7 +164,7 @@ func listenToDockerSocket(parentCtx context.Context, term *terminal.Mux, cfg *Co l.Close() }() - _ = os.Chown(fn, gitpodUID, gitpodGID) + _ = os.Chown(fn, int(cfg.WorkspaceLinuxUID), int(cfg.WorkspaceLinuxGID)) var lastExitErrorTime time.Time burstAttempts := 0 @@ -267,11 +267,11 @@ func listenToDockerSocket(parentCtx context.Context, term *terminal.Mux, cfg *Co return ctx.Err() } -func openDockerUpLogFile() (*os.File, error) { +func openDockerUpLogFile(uid, gid int) (*os.File, error) { if err := os.MkdirAll(logsDir, 0755); err != nil { return nil, xerrors.Errorf("cannot create logs dir: %w", err) } - if err := os.Chown(logsDir, gitpodUID, gitpodGID); err != nil { + if err := os.Chown(logsDir, uid, gid); err != nil { return nil, xerrors.Errorf("cannot chown logs dir: %w", err) } logFile, err := os.OpenFile(dockerUpLogFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) @@ -279,7 +279,7 @@ func openDockerUpLogFile() (*os.File, error) { return nil, xerrors.Errorf("cannot open docker-up log file: %w", err) } - if err := os.Chown(dockerUpLogFilePath, gitpodUID, gitpodGID); err != nil { + if err := os.Chown(dockerUpLogFilePath, uid, gid); err != nil { _ = logFile.Close() return nil, xerrors.Errorf("cannot chown docker-up log file: %w", err) } diff --git a/components/supervisor/pkg/supervisor/git.go b/components/supervisor/pkg/supervisor/git.go index 229134f29a3f48..14cc988986bb99 100644 --- a/components/supervisor/pkg/supervisor/git.go +++ b/components/supervisor/pkg/supervisor/git.go @@ -92,7 +92,7 @@ func (p *GitTokenProvider) openAccessControl() error { return err } gpCmd := exec.Command(gpPath, "preview", "--external", p.workspaceConfig.GitpodHost+"/access-control") - runAsGitpodUser(gpCmd) + runAsUser(gpCmd, p.workspaceConfig.WorkspaceLinuxUID, p.workspaceConfig.WorkspaceLinuxGID) if b, err := gpCmd.CombinedOutput(); err != nil { log.WithField("Stdout", string(b)).WithError(err).Error("failed to exec gp preview to open access control") return err diff --git a/components/supervisor/pkg/supervisor/services.go b/components/supervisor/pkg/supervisor/services.go index 5b051aa6ce4e5c..4f0d8ce6f054fe 100644 --- a/components/supervisor/pkg/supervisor/services.go +++ b/components/supervisor/pkg/supervisor/services.go @@ -741,7 +741,7 @@ func (is *InfoService) WorkspaceInfo(ctx context.Context, req *api.WorkspaceInfo } } - resp.UserHome = "/home/gitpod" + resp.UserHome = os.Getenv("HOME") endpoint, host, err := is.cfg.GitpodAPIEndpoint() if err != nil { @@ -763,6 +763,8 @@ type ControlService struct { publicKey string hostKey *api.SSHPublicKey + uid, gid int + api.UnimplementedControlServiceServer } @@ -792,7 +794,7 @@ func (ss *ControlService) CreateSSHKeyPair(ctx context.Context, req *api.CreateS if err != nil { return xerrors.Errorf("cannot read file ~/.ssh/authorized_keys: %w", err) } - if !bytes.Contains(data, []byte(ss.publicKey)) { + if !bytes.Contains(data, []byte(c.publicKey)) { return xerrors.Errorf("not found special publickey") } return nil @@ -800,8 +802,8 @@ func (ss *ControlService) CreateSSHKeyPair(ctx context.Context, req *api.CreateS err := checkKey() if err == nil { return &api.CreateSSHKeyPairResponse{ - PrivateKey: ss.privateKey, - HostKey: ss.hostKey, + PrivateKey: c.privateKey, + HostKey: c.hostKey, }, nil } log.WithError(err).Error("check authorized_keys failed, will recreate") @@ -812,7 +814,7 @@ func (ss *ControlService) CreateSSHKeyPair(ctx context.Context, req *api.CreateS if err != nil { return nil, xerrors.Errorf("cannot create tmpfile: %w", err) } - err = prepareSSHKey(ctx, filepath.Join(dir, "ssh")) + err = prepareSSHKey(ctx, filepath.Join(dir, "ssh"), c.uid, c.gid) if err != nil { return nil, xerrors.Errorf("cannot create ssh key pair: %w", err) } @@ -836,7 +838,7 @@ func (ss *ControlService) CreateSSHKeyPair(ctx context.Context, req *api.CreateS if err != nil { return nil, xerrors.Errorf("cannot write file ~.ssh/authorized_keys: %w", err) } - err = os.Chown(filepath.Join(home, ".ssh/authorized_keys"), gitpodUID, gitpodGID) + err = os.Chown(filepath.Join(home, ".ssh/authorized_keys"), c.uid, c.gid) if err != nil { return nil, xerrors.Errorf("cannot chown SSH authorized_keys file: %w", err) } @@ -846,8 +848,8 @@ func (ss *ControlService) CreateSSHKeyPair(ctx context.Context, req *api.CreateS if err != nil { return nil, status.Errorf(codes.Internal, "cannot create ssh key pair: %v", err) } - ss.privateKey = string(generated.PrivateKey) - ss.publicKey = string(generated.PublicKey) + c.privateKey = string(generated.PrivateKey) + c.publicKey = string(generated.PublicKey) hostKey, err := os.ReadFile("/.supervisor/ssh/sshkey.pub") if err != nil { @@ -855,7 +857,7 @@ func (ss *ControlService) CreateSSHKeyPair(ctx context.Context, req *api.CreateS } else { hostKeyParts := strings.Split(string(hostKey), " ") if len(hostKeyParts) >= 2 { - ss.hostKey = &api.SSHPublicKey{ + c.hostKey = &api.SSHPublicKey{ Type: hostKeyParts[0], Value: hostKeyParts[1], } diff --git a/components/supervisor/pkg/supervisor/ssh.go b/components/supervisor/pkg/supervisor/ssh.go index e48d33a486f276..28b46546d84af6 100644 --- a/components/supervisor/pkg/supervisor/ssh.go +++ b/components/supervisor/pkg/supervisor/ssh.go @@ -30,7 +30,7 @@ func newSSHServer(ctx context.Context, cfg *Config, envvars []string) (*sshServe sshkey := filepath.Join(filepath.Dir(bin), "ssh", "sshkey") if _, err := os.Stat(sshkey); err != nil { - err := prepareSSHKey(ctx, sshkey) + err := prepareSSHKey(ctx, sshkey, int(cfg.WorkspaceLinuxUID), int(cfg.WorkspaceLinuxGID)) if err != nil { return nil, xerrors.Errorf("unexpected error creating SSH key: %w", err) } @@ -145,7 +145,7 @@ func (s *sshServer) handleConn(ctx context.Context, conn net.Conn) { log.WithField("args", args).Debug("sshd flags") cmd := exec.CommandContext(ctx, openssh, args...) - cmd = runAsGitpodUser(cmd) + cmd = runAsUser(cmd, s.cfg.WorkspaceLinuxUID, s.cfg.WorkspaceLinuxGID) cmd.Env = s.envvars cmd.ExtraFiles = []*os.File{socketFD} cmd.Stderr = os.Stderr @@ -183,7 +183,7 @@ func (s *sshServer) handleConn(ctx context.Context, conn net.Conn) { } } -func prepareSSHKey(ctx context.Context, sshkey string) error { +func prepareSSHKey(ctx context.Context, sshkey string, uid, gid int) error { bin, err := os.Executable() if err != nil { return xerrors.Errorf("cannot find executable path: %w", err) @@ -219,7 +219,7 @@ func prepareSSHKey(ctx context.Context, sshkey string) error { return xerrors.Errorf("cannot create SSH hostkey file: %w", err) } - err = os.Chown(sshkey, gitpodUID, gitpodGID) + err = os.Chown(sshkey, uid, gid) if err != nil { return xerrors.Errorf("cannot chown SSH hostkey file: %w", err) } @@ -235,7 +235,7 @@ func ensureSSHDir(cfg *Config) error { if err != nil { return xerrors.Errorf("cannot create $HOME/.ssh: %w", err) } - _ = exec.Command("chown", "-R", fmt.Sprintf("%d:%d", gitpodUID, gitpodGID), d).Run() + _ = exec.Command("chown", "-R", fmt.Sprintf("%d:%d", cfg.WorkspaceLinuxUID, cfg.WorkspaceLinuxGID), d).Run() return nil } diff --git a/components/supervisor/pkg/supervisor/supervisor.go b/components/supervisor/pkg/supervisor/supervisor.go index 15c6e371868cdc..d486edf7f61fd9 100644 --- a/components/supervisor/pkg/supervisor/supervisor.go +++ b/components/supervisor/pkg/supervisor/supervisor.go @@ -5,6 +5,7 @@ package supervisor import ( + "bytes" "context" "crypto/rand" "crypto/rsa" @@ -13,7 +14,6 @@ import ( "fmt" "io" "io/fs" - "io/ioutil" "math" "net" "net/http" @@ -55,6 +55,7 @@ import ( csapi "github.com/gitpod-io/gitpod/content-service/api" "github.com/gitpod-io/gitpod/content-service/pkg/executor" "github.com/gitpod-io/gitpod/content-service/pkg/git" + "github.com/gitpod-io/gitpod/content-service/pkg/initializer" gitpod "github.com/gitpod-io/gitpod/gitpod-protocol" "github.com/gitpod-io/gitpod/supervisor/api" "github.com/gitpod-io/gitpod/supervisor/pkg/config" @@ -72,12 +73,8 @@ import ( ) const ( - gitpodUID = 33333 - gitpodUserName = "gitpod" - gitpodGID = 33333 - gitpodGroupName = "gitpod" - desktopIDEPort = 24000 - debugProxyPort = 23003 + desktopIDEPort = 24000 + debugProxyPort = 23003 ) var ( @@ -186,13 +183,20 @@ func Run(options ...RunOption) { return } + // Note(cw): legacy rungp behaviour + if opts.RunGP { + cfg.WorkspaceRuntime = WorkspaceRuntimeRunGP + } + // BEWARE: we can only call buildChildProcEnv once, because it might download env vars from a one-time-secret // URL, which would fail if we tried another time. childProcEnvvars = buildChildProcEnv(cfg, nil, opts.RunGP) - err = AddGitpodUserIfNotExists() - if err != nil { - log.WithError(err).Fatal("cannot ensure Gitpod user exists") + if cfg.WorkspaceLinuxUID == legacyGitpodUID || cfg.WorkspaceLinuxGID == legacyGitpodGID { + err = AddGitpodUserIfNotExists() + if err != nil { + log.WithError(err).Fatal("cannot ensure Gitpod user exists") + } } symlinkBinaries(cfg) @@ -203,7 +207,7 @@ func Run(options ...RunOption) { tokenService := NewInMemoryTokenService() - if !opts.RunGP { + if cfg.WorkspaceRuntime != WorkspaceRuntimeRunGP { tkns, err := cfg.GetTokens(true) if err != nil { log.WithError(err).Warn("cannot prepare tokens") @@ -267,7 +271,7 @@ func Run(options ...RunOption) { notificationService = NewNotificationService() ) - if !opts.RunGP { + if cfg.WorkspaceRuntime != WorkspaceRuntimeRunGP { gitpodService = serverapi.NewServerApiService(ctx, &serverapi.ServiceConfig{ Host: host, Endpoint: endpoint, @@ -282,7 +286,7 @@ func Run(options ...RunOption) { if cfg.GetDesktopIDE() != nil { desktopIdeReady = &ideReadyState{cond: sync.NewCond(&sync.Mutex{})} } - if !cfg.isHeadless() && !opts.RunGP && !cfg.isDebugWorkspace() { + if !cfg.isHeadless() && cfg.WorkspaceRuntime != WorkspaceRuntimeRunGP && !cfg.isDebugWorkspace() { go trackReadiness(ctx, telemetry, cfg, cstate, ideReady, desktopIdeReady) } tokenService.provider[KindGit] = []tokenProvider{NewGitTokenProvider(gitpodService, cfg.WorkspaceConfig, notificationService)} @@ -292,7 +296,7 @@ func Run(options ...RunOption) { var exposedPorts ports.ExposedPortsInterface - if !opts.RunGP { + if cfg.WorkspaceRuntime != WorkspaceRuntimeRunGP { exposedPorts = createExposedPortsImpl(cfg, gitpodService) } @@ -307,18 +311,18 @@ func Run(options ...RunOption) { ) topService := NewTopService() - if !opts.RunGP { + if cfg.WorkspaceRuntime == WorkspaceRuntimeContainer { topService.Observe(ctx) } - if !cfg.isHeadless() && !opts.RunGP { + if !cfg.isHeadless() && cfg.WorkspaceRuntime != WorkspaceRuntimeRunGP { go analyseConfigChanges(ctx, cfg, telemetry, gitpodConfigService) go analysePerfChanges(ctx, cfg, telemetry, topService) } supervisorMetrics := metrics.NewMetrics() var metricsReporter *metrics.GrpcMetricsReporter - if !opts.RunGP && !cfg.isDebugWorkspace() && !strings.Contains("ephemeral", cfg.WorkspaceClusterHost) { + if cfg.WorkspaceRuntime != WorkspaceRuntimeRunGP && !cfg.isDebugWorkspace() && !strings.Contains("ephemeral", cfg.WorkspaceClusterHost) { _, gitpodHost, err := cfg.GitpodAPIEndpoint() if err != nil { log.WithError(err).Error("grpc metrics: failed to parse gitpod host") @@ -350,15 +354,15 @@ func Run(options ...RunOption) { } termMuxSrv.Env = childProcEnvvars termMuxSrv.DefaultCreds = &syscall.Credential{ - Uid: gitpodUID, - Gid: gitpodGID, + Uid: cfg.WorkspaceLinuxUID, + Gid: cfg.WorkspaceLinuxGID, } taskManager := newTasksManager(cfg, termMuxSrv, cstate, nil, ideReady, desktopIdeReady) gitStatusWg := &sync.WaitGroup{} gitStatusCtx, stopGitStatus := context.WithCancel(ctx) - if !cfg.isPrebuild() && !cfg.isHeadless() && !opts.RunGP && !cfg.isDebugWorkspace() { + if !cfg.isPrebuild() && !cfg.isHeadless() && cfg.WorkspaceRuntime != WorkspaceRuntimeRunGP && !cfg.isDebugWorkspace() { gitStatusWg.Add(1) gitStatusService := &GitStatusService{ cfg: cfg, @@ -385,7 +389,7 @@ func Run(options ...RunOption) { RegistrableTokenService{Service: tokenService}, notificationService, &InfoService{cfg: cfg, ContentState: cstate, GitpodService: gitpodService}, - &ControlService{portsManager: portMgmt}, + &ControlService{portsManager: portMgmt, uid: int(cfg.WorkspaceLinuxUID), gid: int(cfg.WorkspaceLinuxGID)}, &portService{portsManager: portMgmt}, } apiServices = append(apiServices, additionalServices...) @@ -409,7 +413,7 @@ func Run(options ...RunOption) { shutdown = make(chan ShutdownReason, 1) ) - if opts.RunGP { + if cfg.WorkspaceRuntime == WorkspaceRuntimeRunGP { cstate.MarkContentReady(csapi.WorkspaceInitFromOther) } else if cfg.isDebugWorkspace() { cstate.MarkContentReady(cfg.GetDebugWorkspaceContentSource()) @@ -428,7 +432,7 @@ func Run(options ...RunOption) { tasksSuccessChan := make(chan taskSuccess, 1) go taskManager.Run(ctx, &wg, tasksSuccessChan) - if !opts.RunGP { + if cfg.WorkspaceRuntime != WorkspaceRuntimeRunGP { wg.Add(1) go socketActivationForDocker(ctx, &wg, termMux, cfg, telemetry, notificationService, cstate) } @@ -436,7 +440,7 @@ func Run(options ...RunOption) { if cfg.isHeadless() { wg.Add(1) go stopWhenTasksAreDone(ctx, &wg, shutdown, tasksSuccessChan) - } else if !opts.RunGP { + } else if cfg.WorkspaceRuntime != WorkspaceRuntimeRunGP { wg.Add(1) go portMgmt.Run(ctx, &wg) } @@ -452,7 +456,7 @@ func Run(options ...RunOption) { }() } - if !cfg.isPrebuild() && !opts.RunGP && !cfg.isDebugWorkspace() { + if !cfg.isPrebuild() && cfg.WorkspaceRuntime != WorkspaceRuntimeRunGP && !cfg.isDebugWorkspace() { go func() { for _, repoRoot := range strings.Split(cfg.RepoRoots, ",") { <-cstate.ContentReady() @@ -463,11 +467,11 @@ func Run(options ...RunOption) { log.Debugf("unshallow of local repository took %v", time.Since(start)) }() - if !isShallowRepository(repoRoot) { + if !isShallowRepository(repoRoot, cfg.WorkspaceLinuxUID, cfg.WorkspaceLinuxGID) { return } - cmd := runAsGitpodUser(exec.Command("git", "fetch", "--unshallow", "--tags")) + cmd := runAsUser(exec.Command("git", "fetch", "--unshallow", "--tags"), cfg.WorkspaceLinuxUID, cfg.WorkspaceLinuxGID) cmd.Dir = repoRoot cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr @@ -504,8 +508,8 @@ func Run(options ...RunOption) { wg.Wait() } -func isShallowRepository(rootDir string) bool { - cmd := runAsGitpodUser(exec.Command("git", "rev-parse", "--is-shallow-repository")) +func isShallowRepository(rootDir string, uid, gid uint32) bool { + cmd := runAsUser(exec.Command("git", "rev-parse", "--is-shallow-repository"), uid, gid) cmd.Dir = rootDir out, err := cmd.CombinedOutput() if err != nil { @@ -528,7 +532,9 @@ func installDotfiles(ctx context.Context, cfg *Config, tokenService *InMemoryTok return } - const dotfilePath = "/home/gitpod/.dotfiles" + home := os.Getenv("HOME") + + dotfilePath := filepath.Join(home, ".dotfiles") if _, err := os.Stat(dotfilePath); err == nil { // dotfile path exists already - nothing to do here return @@ -536,15 +542,15 @@ func installDotfiles(ctx context.Context, cfg *Config, tokenService *InMemoryTok prep := func(cfg *Config, out io.Writer, name string, args ...string) *exec.Cmd { cmd := exec.Command(name, args...) - cmd.Dir = "/home/gitpod" - runAsGitpodUser(cmd) + cmd.Dir = home + runAsUser(cmd, cfg.WorkspaceLinuxUID, cfg.WorkspaceLinuxGID) cmd.Stdout = out cmd.Stderr = out return cmd } err := func() (err error) { - out, err := os.OpenFile("/home/gitpod/.dotfiles.log", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644) + out, err := os.OpenFile(dotfilePath+".log", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644) if err != nil { return err } @@ -596,7 +602,7 @@ func installDotfiles(ctx context.Context, cfg *Config, tokenService *InMemoryTok filepath.Walk(dotfilePath, func(name string, info os.FileInfo, err error) error { if err == nil { - err = os.Chown(name, gitpodUID, gitpodGID) + err = os.Chown(name, int(cfg.WorkspaceLinuxUID), int(cfg.WorkspaceLinuxGID)) } return err }) @@ -658,7 +664,7 @@ func installDotfiles(ctx context.Context, cfg *Config, tokenService *InMemoryTok return nil } - homeFN := filepath.Join("/home/gitpod", strings.TrimPrefix(path, dotfilePath)) + homeFN := filepath.Join(os.Getenv("HOME"), strings.TrimPrefix(path, dotfilePath)) if _, err := os.Stat(homeFN); err == nil { // homeFN exists already - do nothing return nil @@ -742,7 +748,7 @@ func configureGit(cfg *Config) { for _, s := range settings { cmd := exec.Command("git", append([]string{"config", "--global"}, s...)...) - cmd = runAsGitpodUser(cmd) + cmd = runAsUser(cmd, cfg.WorkspaceLinuxUID, cfg.WorkspaceLinuxGID) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr err := cmd.Run() @@ -927,7 +933,7 @@ func prepareIDELaunch(cfg *Config, ideConfig *IDEConfig) *exec.Cmd { // All supervisor children run as gitpod user. The environment variables we produce are also // gitpod user specific. - runAsGitpodUser(cmd) + runAsUser(cmd, cfg.WorkspaceLinuxUID, cfg.WorkspaceLinuxGID) // We need the child process to run in its own process group, s.t. we can suspend and resume // IDE and its children. @@ -1025,8 +1031,10 @@ func buildChildProcEnv(cfg *Config, envvars []string, runGP bool) []string { // - https://github.com/mirror/busybox/blob/24198f652f10dca5603df7c704263358ca21f5ce/libbb/setup_environment.c#L32 // - https://github.com/mirror/busybox/blob/24198f652f10dca5603df7c704263358ca21f5ce/libbb/login.c#L140-L170 // - envs["HOME"] = "/home/gitpod" - envs["USER"] = "gitpod" + if cfg.WorkspaceRuntime != WorkspaceRuntimeNextgen { + envs["HOME"] = "/home/gitpod" + envs["USER"] = "gitpod" + } // Particular Java optimisation: Java pre v10 did not gauge it's available memory correctly, and needed explicitly setting "-Xmx" for all Hotspot/openJDK VMs if mem, ok := envs["GITPOD_MEMORY"]; ok { @@ -1160,7 +1168,7 @@ func ideStatusRequest(url string) ([]byte, error) { return nil, xerrors.Errorf("IDE readiness probe came back with non-200 status code (%v)", resp.StatusCode) } - body, err := ioutil.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) if err != nil { return nil, err } @@ -1483,7 +1491,7 @@ func stopWhenTasksAreDone(ctx context.Context, wg *sync.WaitGroup, shutdown chan if success.Failed() { // we signal task failure via kubernetes termination log msg := []byte("headless task failed: " + string(success)) - err := ioutil.WriteFile("/dev/termination-log", msg, 0o644) + err := os.WriteFile("/dev/termination-log", msg, 0o644) if err != nil { log.WithError(err).Error("err while writing termination log") } @@ -1534,14 +1542,15 @@ func startContentInit(ctx context.Context, cfg *Config, wg *sync.WaitGroup, cst fn := "/workspace/.gitpod/content.json" fnReady := "/workspace/.gitpod/ready" - contentFile, err := os.Open(fn) - if err != nil { - if !os.IsNotExist(err) { - log.WithError(err).Error("cannot open init descriptor") - return - } - - log.Infof("%s does not exist, going to wait for %s", fn, fnReady) + contentFile, err := os.ReadFile(fn) + if os.IsNotExist(err) { + contentFile = []byte(cfg.ContentInitializer) + } else if err != nil { + log.WithError(err).Error("cannot open init descriptor") + return + } + if len(contentFile) == 0 { + log.Infof("no content initializer provided, waiting for %s", fnReady) // If there is no content descriptor the content must have come from somewhere (i.e. a layer or ws-daemon). // Let's wait for that to happen. @@ -1573,11 +1582,23 @@ func startContentInit(ctx context.Context, cfg *Config, wg *sync.WaitGroup, cst return } - defer contentFile.Close() - log.Info("supervisor: running content service executor with content descriptor") - var src csapi.WorkspaceInitSource - src, err = executor.Execute(ctx, "/workspace", contentFile, true) + var ( + src csapi.WorkspaceInitSource + user *initializer.User + ) + if cfg.WorkspaceRuntime == WorkspaceRuntimeNextgen { + user = &initializer.User{ + UID: cfg.WorkspaceLinuxUID, + GID: cfg.WorkspaceLinuxGID, + } + } else { + user = &initializer.User{ + UID: legacyGitpodUID, + GID: legacyGitpodGID, + } + } + src, err = executor.Execute(ctx, "/workspace", bytes.NewReader(contentFile), user) if err != nil { return } @@ -1805,7 +1826,7 @@ func trackReadiness(ctx context.Context, w analytics.Writer, cfg *Config, cstate } } -func runAsGitpodUser(cmd *exec.Cmd) *exec.Cmd { +func runAsUser(cmd *exec.Cmd, uid, gid uint32) *exec.Cmd { if cmd.SysProcAttr == nil { cmd.SysProcAttr = &syscall.SysProcAttr{} } @@ -1813,8 +1834,8 @@ func runAsGitpodUser(cmd *exec.Cmd) *exec.Cmd { cmd.SysProcAttr.Credential = &syscall.Credential{} } cmd.Env = append(cmd.Env, childProcEnvvars...) - cmd.SysProcAttr.Credential.Uid = gitpodUID - cmd.SysProcAttr.Credential.Gid = gitpodGID + cmd.SysProcAttr.Credential.Uid = uid + cmd.SysProcAttr.Credential.Gid = gid return cmd } diff --git a/components/supervisor/pkg/supervisor/user.go b/components/supervisor/pkg/supervisor/user.go index 587d73c0c26d95..fbfb9e8863d536 100644 --- a/components/supervisor/pkg/supervisor/user.go +++ b/components/supervisor/pkg/supervisor/user.go @@ -35,26 +35,33 @@ func (osLookup) LookupId(id string) (grp *user.User, err error) { return u var defaultLookup lookup = osLookup{} +const ( + legacyGitpodUID = 33333 + legacyGitpodGID = 33333 + legacyGitpodUserName = "gitpod" + legacyGitpodGroupName = "gitpod" +) + func AddGitpodUserIfNotExists() error { - ok, err := hasGroup(gitpodGroupName, gitpodGID) + ok, err := hasGroup(legacyGitpodGroupName, legacyGitpodGID) if err != nil { return err } if !ok { - err = addGroup(gitpodGroupName, gitpodGID) + err = addGroup(legacyGitpodGroupName, legacyGitpodGID) if err != nil { return err } } - if err := addSudoer(gitpodGroupName); err != nil { + if err := addSudoer(legacyGitpodGroupName); err != nil { log.WithError(err).Error("add gitpod sudoers") } targetUser := &user.User{ - Uid: strconv.Itoa(gitpodUID), - Gid: strconv.Itoa(gitpodGID), - Username: gitpodUserName, - HomeDir: "/home/" + gitpodUserName, + Uid: strconv.Itoa(legacyGitpodUID), + Gid: strconv.Itoa(legacyGitpodGID), + Username: legacyGitpodUserName, + HomeDir: "/home/" + legacyGitpodUserName, } ok, err = hasUser(targetUser) if err != nil { @@ -93,7 +100,7 @@ func hasGroup(name string, gid int) (bool, error) { } if grpByID == nil && grpByName != nil { // a group with this name already exists, but has a different GID - return true, xerrors.Errorf("group named %s exists but uses different GID %s, should be: %d", name, grpByName.Gid, gitpodGID) + return true, xerrors.Errorf("group named %s exists but uses different GID %s, should be: %d", name, grpByName.Gid, gid) } // group exists and all is well @@ -127,7 +134,7 @@ func hasUser(u *user.User) (bool, error) { } if userByID == nil && userByName != nil { // a user with this name already exists, but has a different GID - return true, xerrors.Errorf("user named %s exists but uses different UID %s, should be: %d", u.Username, userByName.Uid, gitpodUID) + return true, xerrors.Errorf("user named %s exists but uses different UID %s, should be: %s", u.Username, userByName.Uid, u.Uid) } // at this point it doesn't matter if we use userByID or byName - they're likely the same diff --git a/components/supervisor/pkg/supervisor/user_test.go b/components/supervisor/pkg/supervisor/user_test.go index 30f0151a05a4c8..772a38824d5e07 100644 --- a/components/supervisor/pkg/supervisor/user_test.go +++ b/components/supervisor/pkg/supervisor/user_test.go @@ -13,7 +13,7 @@ import ( ) func TestHasUser(t *testing.T) { - gitpodUser := user.User{Username: gitpodUserName, Uid: strconv.Itoa(gitpodUID), HomeDir: "/home/gitpod", Gid: strconv.Itoa(gitpodGID)} + gitpodUser := user.User{Username: legacyGitpodUserName, Uid: strconv.Itoa(legacyGitpodUID), HomeDir: "/home/gitpod", Gid: strconv.Itoa(legacyGitpodGID)} mod := func(u user.User, m func(u *user.User)) *user.User { m(&u) return &u @@ -39,15 +39,15 @@ func TestHasUser(t *testing.T) { { Name: "does not exist", Ops: ops{ - RLookup: opsResult{Err: user.UnknownUserError(gitpodUserName)}, - RLookupId: opsResult{Err: user.UnknownUserIdError(gitpodUID)}, + RLookup: opsResult{Err: user.UnknownUserError(legacyGitpodUserName)}, + RLookupId: opsResult{Err: user.UnknownUserIdError(legacyGitpodUID)}, }, }, { Name: "different UID", Ops: ops{ RLookup: opsResult{User: mod(gitpodUser, func(u *user.User) { u.Uid = "1000" })}, - RLookupId: opsResult{Err: user.UnknownUserIdError(gitpodUID)}, + RLookupId: opsResult{Err: user.UnknownUserIdError(legacyGitpodUID)}, }, Expectation: Expectation{ Exists: true, @@ -57,7 +57,7 @@ func TestHasUser(t *testing.T) { { Name: "different name", Ops: ops{ - RLookup: opsResult{Err: user.UnknownUserError(gitpodUserName)}, + RLookup: opsResult{Err: user.UnknownUserError(legacyGitpodUserName)}, RLookupId: opsResult{User: mod(gitpodUser, func(u *user.User) { u.Username = "foobar" })}, }, Expectation: Expectation{ @@ -107,7 +107,7 @@ func TestHasUser(t *testing.T) { } func TestHasGroup(t *testing.T) { - gitpodGroup := user.Group{Name: gitpodGroupName, Gid: strconv.Itoa(gitpodGID)} + gitpodGroup := user.Group{Name: legacyGitpodGroupName, Gid: strconv.Itoa(legacyGitpodGID)} mod := func(u user.Group, m func(u *user.Group)) *user.Group { m(&u) return &u @@ -133,7 +133,7 @@ func TestHasGroup(t *testing.T) { { Name: "does not exist", Ops: ops{ - RLookupGroup: opsResult{Err: user.UnknownGroupError(gitpodGroupName)}, + RLookupGroup: opsResult{Err: user.UnknownGroupError(legacyGitpodGroupName)}, RLookupGroupId: opsResult{Err: user.UnknownGroupIdError(gitpodGroup.Gid)}, }, }, @@ -151,7 +151,7 @@ func TestHasGroup(t *testing.T) { { Name: "different name", Ops: ops{ - RLookupGroup: opsResult{Err: user.UnknownGroupError(gitpodGroupName)}, + RLookupGroup: opsResult{Err: user.UnknownGroupError(legacyGitpodGroupName)}, RLookupGroupId: opsResult{Group: mod(gitpodGroup, func(u *user.Group) { u.Name = "foobar" })}, }, Expectation: Expectation{ @@ -164,7 +164,7 @@ func TestHasGroup(t *testing.T) { for _, test := range tests { t.Run(test.Name, func(t *testing.T) { defaultLookup = test.Ops - exists, err := hasGroup(gitpodGroupName, gitpodGID) + exists, err := hasGroup(legacyGitpodGroupName, legacyGitpodGID) var act Expectation act.Exists = exists if err != nil { diff --git a/components/ws-daemon/pkg/content/initializer.go b/components/ws-daemon/pkg/content/initializer.go index 5ff3a2a0496c4b..f2c60cdc48685a 100644 --- a/components/ws-daemon/pkg/content/initializer.go +++ b/components/ws-daemon/pkg/content/initializer.go @@ -329,7 +329,7 @@ func RunInitializerChild() (err error) { rs := &remoteContentStorage{RemoteContent: initmsg.RemoteContent} dst := initmsg.Destination - initializer, err := wsinit.NewFromRequest(ctx, dst, rs, &req, wsinit.NewFromRequestOpts{ForceGitpodUserForGit: false}) + initializer, err := wsinit.NewFromRequest(ctx, dst, rs, &req, wsinit.NewFromRequestOpts{}) if err != nil { return err }