diff --git a/components/content-service/pkg/executor/executor.go b/components/content-service/pkg/executor/executor.go index f04b5fcbeb3b0b..66a7fd3d1ba080 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, runAs *initializer.User, opts ...initializer.InitializeOpt) (src csapi.WorkspaceInitSource, err error) { +func Execute(ctx context.Context, destination string, cfgin io.Reader, forceGitUser bool, 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, runAs *in rs = &storage.NamedURLDownloader{URLs: cfg.URLs} ilr, err = initializer.NewFromRequest(ctx, destination, rs, &req, initializer.NewFromRequestOpts{ - RunAs: runAs, + ForceGitpodUserForGit: forceGitUser, }) if err != nil { return "", err diff --git a/components/content-service/pkg/git/git.go b/components/content-service/pkg/git/git.go index a1970331a7f3d4..3a74d5a5778155 100644 --- a/components/content-service/pkg/git/git.go +++ b/components/content-service/pkg/git/git.go @@ -13,7 +13,6 @@ import ( "os/exec" "path/filepath" "strings" - "syscall" "github.com/opentracing/opentracing-go" "golang.org/x/xerrors" @@ -94,12 +93,8 @@ type Client struct { // UpstreamCloneURI is the fork upstream of a repository UpstreamRemoteURI string - // RunAs runs the Git commands as a particular user - if not nil - RunAs *User -} - -type User struct { - UID, GID uint32 + // if true will run git command as gitpod user (should be executed as root that has access to sudo in this case) + RunAsGitpodUser bool } // Status describes the status of a Git repo/working copy akin to "git status" @@ -183,6 +178,8 @@ 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...) @@ -204,19 +201,13 @@ 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 e372f0296f91cc..f96e81375aec84 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.RunAs != nil { - args := []string{fmt.Sprintf("%d:%d", ws.RunAs.UID, ws.RunAs.GID), ws.Location} + if ws.RunAsGitpodUser { + args := []string{"gitpod", 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 8ee0d0f1f0f460..d75cb67de1aab4 100644 --- a/components/content-service/pkg/initializer/initializer.go +++ b/components/content-service/pkg/initializer/initializer.go @@ -99,13 +99,11 @@ func (e CompositeInitializer) Run(ctx context.Context, mappings []archive.IDMapp // NewFromRequestOpts configures the initializer produced from a content init request type NewFromRequestOpts struct { - // RunAs - if not nil - decides the user with which the initiallisation will be executed - RunAs *User -} - -type User struct { - UID uint32 - GID uint32 + // 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 } // NewFromRequest picks the initializer from the request but does not execute it. @@ -134,7 +132,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.RunAs) + initializer, err = newGitInitializer(ctx, loc, ir.Git, opts.ForceGitpodUserForGit) } else if ir, ok := spec.(*csapi.WorkspaceInitializer_Prebuild); ok { if ir.Prebuild == nil { return nil, status.Error(codes.InvalidArgument, "missing prebuild initializer spec") @@ -148,7 +146,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.RunAs) + gitinit, err := newGitInitializer(ctx, loc, gi, opts.ForceGitpodUserForGit) if err != nil { return nil, err } @@ -251,7 +249,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, runAs *User) (*GitInitializer, error) { +func newGitInitializer(ctx context.Context, loc string, req *csapi.GitInitializer, forceGitpodUser bool) (*GitInitializer, error) { if req.Config == nil { return nil, status.Error(codes.InvalidArgument, "Git initializer misses config") } @@ -296,11 +294,6 @@ 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{ @@ -310,7 +303,7 @@ func newGitInitializer(ctx context.Context, loc string, req *csapi.GitInitialize Config: req.Config.CustomConfig, AuthMethod: authMethod, AuthProvider: authProvider, - RunAs: user, + RunAsGitpodUser: forceGitpodUser, }, TargetMode: targetMode, CloneTarget: req.CloneTaget, diff --git a/components/supervisor/cmd/dump-initializer.go b/components/supervisor/cmd/dump-initializer.go deleted file mode 100644 index f5c61177c53c1a..00000000000000 --- a/components/supervisor/cmd/dump-initializer.go +++ /dev/null @@ -1,38 +0,0 @@ -// 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 c676b9641ea4fb..94c0ecbc771208 100644 --- a/components/supervisor/pkg/supervisor/config.go +++ b/components/supervisor/pkg/supervisor/config.go @@ -8,6 +8,7 @@ import ( "encoding/json" "fmt" "io" + "io/ioutil" "math" "net/http" "net/url" @@ -328,35 +329,6 @@ 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. @@ -615,7 +587,7 @@ func loadDesktopIDEs(static *StaticConfig) ([]*IDEConfig, error) { uniqueDesktopIDEs[desktopIDE.Name] = struct{}{} } - files, err := os.ReadDir(static.DesktopIDERoot) + files, err := ioutil.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 a3650de79e2802..4b25873ca66a8f 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(int(cfg.WorkspaceLinuxUID), int(cfg.WorkspaceLinuxGID)) + logFile, err := openDockerUpLogFile() 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, int(cfg.WorkspaceLinuxUID), int(cfg.WorkspaceLinuxGID)) + _ = os.Chown(fn, gitpodUID, gitpodGID) 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(uid, gid int) (*os.File, error) { +func openDockerUpLogFile() (*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, uid, gid); err != nil { + if err := os.Chown(logsDir, gitpodUID, gitpodGID); 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(uid, gid int) (*os.File, error) { return nil, xerrors.Errorf("cannot open docker-up log file: %w", err) } - if err := os.Chown(dockerUpLogFilePath, uid, gid); err != nil { + if err := os.Chown(dockerUpLogFilePath, gitpodUID, gitpodGID); 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 3b6c57a58c287e..2de219c35c671a 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") - runAsUser(gpCmd, p.workspaceConfig.WorkspaceLinuxUID, p.workspaceConfig.WorkspaceLinuxGID) + runAsGitpodUser(gpCmd) 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 4e9a45dfc4afec..6c029265c810c4 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 = os.Getenv("HOME") + resp.UserHome = "/home/gitpod" endpoint, host, err := is.cfg.GitpodAPIEndpoint() if err != nil { @@ -763,8 +763,6 @@ type ControlService struct { publicKey string hostKey *api.SSHPublicKey - uid, gid int - api.UnimplementedControlServiceServer } @@ -785,15 +783,15 @@ func (c *ControlService) ExposePort(ctx context.Context, req *api.ExposePortRequ } // CreateSSHKeyPair create a ssh key pair for the workspace. -func (c *ControlService) CreateSSHKeyPair(ctx context.Context, req *api.CreateSSHKeyPairRequest) (response *api.CreateSSHKeyPairResponse, err error) { - home := os.Getenv("HOME") - if c.privateKey != "" && c.publicKey != "" { +func (ss *ControlService) CreateSSHKeyPair(ctx context.Context, req *api.CreateSSHKeyPairRequest) (response *api.CreateSSHKeyPairResponse, err error) { + home := "/home/gitpod/" + if ss.privateKey != "" && ss.publicKey != "" { checkKey := func() error { data, err := os.ReadFile(filepath.Join(home, ".ssh/authorized_keys")) if err != nil { return xerrors.Errorf("cannot read file ~/.ssh/authorized_keys: %w", err) } - if !bytes.Contains(data, []byte(c.publicKey)) { + if !bytes.Contains(data, []byte(ss.publicKey)) { return xerrors.Errorf("not found special publickey") } return nil @@ -801,8 +799,8 @@ func (c *ControlService) CreateSSHKeyPair(ctx context.Context, req *api.CreateSS err := checkKey() if err == nil { return &api.CreateSSHKeyPairResponse{ - PrivateKey: c.privateKey, - HostKey: c.hostKey, + PrivateKey: ss.privateKey, + HostKey: ss.hostKey, }, nil } log.WithError(err).Error("check authorized_keys failed, will recreate") @@ -813,7 +811,7 @@ func (c *ControlService) CreateSSHKeyPair(ctx context.Context, req *api.CreateSS if err != nil { return nil, xerrors.Errorf("cannot create tmpfile: %w", err) } - err = prepareSSHKey(ctx, filepath.Join(dir, "ssh"), c.uid, c.gid) + err = prepareSSHKey(ctx, filepath.Join(dir, "ssh")) if err != nil { return nil, xerrors.Errorf("cannot create ssh key pair: %w", err) } @@ -837,7 +835,7 @@ func (c *ControlService) CreateSSHKeyPair(ctx context.Context, req *api.CreateSS if err != nil { return nil, xerrors.Errorf("cannot write file ~.ssh/authorized_keys: %w", err) } - err = os.Chown(filepath.Join(home, ".ssh/authorized_keys"), c.uid, c.gid) + err = os.Chown(filepath.Join(home, ".ssh/authorized_keys"), gitpodUID, gitpodGID) if err != nil { return nil, xerrors.Errorf("cannot chown SSH authorized_keys file: %w", err) } @@ -847,8 +845,8 @@ func (c *ControlService) CreateSSHKeyPair(ctx context.Context, req *api.CreateSS if err != nil { return nil, status.Errorf(codes.Internal, "cannot create ssh key pair: %v", err) } - c.privateKey = string(generated.PrivateKey) - c.publicKey = string(generated.PublicKey) + ss.privateKey = string(generated.PrivateKey) + ss.publicKey = string(generated.PublicKey) hostKey, err := os.ReadFile("/.supervisor/ssh/sshkey.pub") if err != nil { @@ -856,7 +854,7 @@ func (c *ControlService) CreateSSHKeyPair(ctx context.Context, req *api.CreateSS } else { hostKeyParts := strings.Split(string(hostKey), " ") if len(hostKeyParts) >= 2 { - c.hostKey = &api.SSHPublicKey{ + ss.hostKey = &api.SSHPublicKey{ Type: hostKeyParts[0], Value: hostKeyParts[1], } @@ -864,8 +862,8 @@ func (c *ControlService) CreateSSHKeyPair(ctx context.Context, req *api.CreateSS } return &api.CreateSSHKeyPairResponse{ - PrivateKey: c.privateKey, - HostKey: c.hostKey, + PrivateKey: ss.privateKey, + HostKey: ss.hostKey, }, err } diff --git a/components/supervisor/pkg/supervisor/ssh.go b/components/supervisor/pkg/supervisor/ssh.go index 28b46546d84af6..e48d33a486f276 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, int(cfg.WorkspaceLinuxUID), int(cfg.WorkspaceLinuxGID)) + err := prepareSSHKey(ctx, sshkey) 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 = runAsUser(cmd, s.cfg.WorkspaceLinuxUID, s.cfg.WorkspaceLinuxGID) + cmd = runAsGitpodUser(cmd) 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, uid, gid int) error { +func prepareSSHKey(ctx context.Context, sshkey string) 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, uid, gid int) error { return xerrors.Errorf("cannot create SSH hostkey file: %w", err) } - err = os.Chown(sshkey, uid, gid) + err = os.Chown(sshkey, gitpodUID, gitpodGID) 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", cfg.WorkspaceLinuxUID, cfg.WorkspaceLinuxGID), d).Run() + _ = exec.Command("chown", "-R", fmt.Sprintf("%d:%d", gitpodUID, gitpodGID), d).Run() return nil } diff --git a/components/supervisor/pkg/supervisor/supervisor.go b/components/supervisor/pkg/supervisor/supervisor.go index d486edf7f61fd9..15c6e371868cdc 100644 --- a/components/supervisor/pkg/supervisor/supervisor.go +++ b/components/supervisor/pkg/supervisor/supervisor.go @@ -5,7 +5,6 @@ package supervisor import ( - "bytes" "context" "crypto/rand" "crypto/rsa" @@ -14,6 +13,7 @@ import ( "fmt" "io" "io/fs" + "io/ioutil" "math" "net" "net/http" @@ -55,7 +55,6 @@ 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" @@ -73,8 +72,12 @@ import ( ) const ( - desktopIDEPort = 24000 - debugProxyPort = 23003 + gitpodUID = 33333 + gitpodUserName = "gitpod" + gitpodGID = 33333 + gitpodGroupName = "gitpod" + desktopIDEPort = 24000 + debugProxyPort = 23003 ) var ( @@ -183,20 +186,13 @@ 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) - if cfg.WorkspaceLinuxUID == legacyGitpodUID || cfg.WorkspaceLinuxGID == legacyGitpodGID { - err = AddGitpodUserIfNotExists() - if err != nil { - log.WithError(err).Fatal("cannot ensure Gitpod user exists") - } + err = AddGitpodUserIfNotExists() + if err != nil { + log.WithError(err).Fatal("cannot ensure Gitpod user exists") } symlinkBinaries(cfg) @@ -207,7 +203,7 @@ func Run(options ...RunOption) { tokenService := NewInMemoryTokenService() - if cfg.WorkspaceRuntime != WorkspaceRuntimeRunGP { + if !opts.RunGP { tkns, err := cfg.GetTokens(true) if err != nil { log.WithError(err).Warn("cannot prepare tokens") @@ -271,7 +267,7 @@ func Run(options ...RunOption) { notificationService = NewNotificationService() ) - if cfg.WorkspaceRuntime != WorkspaceRuntimeRunGP { + if !opts.RunGP { gitpodService = serverapi.NewServerApiService(ctx, &serverapi.ServiceConfig{ Host: host, Endpoint: endpoint, @@ -286,7 +282,7 @@ func Run(options ...RunOption) { if cfg.GetDesktopIDE() != nil { desktopIdeReady = &ideReadyState{cond: sync.NewCond(&sync.Mutex{})} } - if !cfg.isHeadless() && cfg.WorkspaceRuntime != WorkspaceRuntimeRunGP && !cfg.isDebugWorkspace() { + if !cfg.isHeadless() && !opts.RunGP && !cfg.isDebugWorkspace() { go trackReadiness(ctx, telemetry, cfg, cstate, ideReady, desktopIdeReady) } tokenService.provider[KindGit] = []tokenProvider{NewGitTokenProvider(gitpodService, cfg.WorkspaceConfig, notificationService)} @@ -296,7 +292,7 @@ func Run(options ...RunOption) { var exposedPorts ports.ExposedPortsInterface - if cfg.WorkspaceRuntime != WorkspaceRuntimeRunGP { + if !opts.RunGP { exposedPorts = createExposedPortsImpl(cfg, gitpodService) } @@ -311,18 +307,18 @@ func Run(options ...RunOption) { ) topService := NewTopService() - if cfg.WorkspaceRuntime == WorkspaceRuntimeContainer { + if !opts.RunGP { topService.Observe(ctx) } - if !cfg.isHeadless() && cfg.WorkspaceRuntime != WorkspaceRuntimeRunGP { + if !cfg.isHeadless() && !opts.RunGP { go analyseConfigChanges(ctx, cfg, telemetry, gitpodConfigService) go analysePerfChanges(ctx, cfg, telemetry, topService) } supervisorMetrics := metrics.NewMetrics() var metricsReporter *metrics.GrpcMetricsReporter - if cfg.WorkspaceRuntime != WorkspaceRuntimeRunGP && !cfg.isDebugWorkspace() && !strings.Contains("ephemeral", cfg.WorkspaceClusterHost) { + if !opts.RunGP && !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") @@ -354,15 +350,15 @@ func Run(options ...RunOption) { } termMuxSrv.Env = childProcEnvvars termMuxSrv.DefaultCreds = &syscall.Credential{ - Uid: cfg.WorkspaceLinuxUID, - Gid: cfg.WorkspaceLinuxGID, + Uid: gitpodUID, + Gid: gitpodGID, } taskManager := newTasksManager(cfg, termMuxSrv, cstate, nil, ideReady, desktopIdeReady) gitStatusWg := &sync.WaitGroup{} gitStatusCtx, stopGitStatus := context.WithCancel(ctx) - if !cfg.isPrebuild() && !cfg.isHeadless() && cfg.WorkspaceRuntime != WorkspaceRuntimeRunGP && !cfg.isDebugWorkspace() { + if !cfg.isPrebuild() && !cfg.isHeadless() && !opts.RunGP && !cfg.isDebugWorkspace() { gitStatusWg.Add(1) gitStatusService := &GitStatusService{ cfg: cfg, @@ -389,7 +385,7 @@ func Run(options ...RunOption) { RegistrableTokenService{Service: tokenService}, notificationService, &InfoService{cfg: cfg, ContentState: cstate, GitpodService: gitpodService}, - &ControlService{portsManager: portMgmt, uid: int(cfg.WorkspaceLinuxUID), gid: int(cfg.WorkspaceLinuxGID)}, + &ControlService{portsManager: portMgmt}, &portService{portsManager: portMgmt}, } apiServices = append(apiServices, additionalServices...) @@ -413,7 +409,7 @@ func Run(options ...RunOption) { shutdown = make(chan ShutdownReason, 1) ) - if cfg.WorkspaceRuntime == WorkspaceRuntimeRunGP { + if opts.RunGP { cstate.MarkContentReady(csapi.WorkspaceInitFromOther) } else if cfg.isDebugWorkspace() { cstate.MarkContentReady(cfg.GetDebugWorkspaceContentSource()) @@ -432,7 +428,7 @@ func Run(options ...RunOption) { tasksSuccessChan := make(chan taskSuccess, 1) go taskManager.Run(ctx, &wg, tasksSuccessChan) - if cfg.WorkspaceRuntime != WorkspaceRuntimeRunGP { + if !opts.RunGP { wg.Add(1) go socketActivationForDocker(ctx, &wg, termMux, cfg, telemetry, notificationService, cstate) } @@ -440,7 +436,7 @@ func Run(options ...RunOption) { if cfg.isHeadless() { wg.Add(1) go stopWhenTasksAreDone(ctx, &wg, shutdown, tasksSuccessChan) - } else if cfg.WorkspaceRuntime != WorkspaceRuntimeRunGP { + } else if !opts.RunGP { wg.Add(1) go portMgmt.Run(ctx, &wg) } @@ -456,7 +452,7 @@ func Run(options ...RunOption) { }() } - if !cfg.isPrebuild() && cfg.WorkspaceRuntime != WorkspaceRuntimeRunGP && !cfg.isDebugWorkspace() { + if !cfg.isPrebuild() && !opts.RunGP && !cfg.isDebugWorkspace() { go func() { for _, repoRoot := range strings.Split(cfg.RepoRoots, ",") { <-cstate.ContentReady() @@ -467,11 +463,11 @@ func Run(options ...RunOption) { log.Debugf("unshallow of local repository took %v", time.Since(start)) }() - if !isShallowRepository(repoRoot, cfg.WorkspaceLinuxUID, cfg.WorkspaceLinuxGID) { + if !isShallowRepository(repoRoot) { return } - cmd := runAsUser(exec.Command("git", "fetch", "--unshallow", "--tags"), cfg.WorkspaceLinuxUID, cfg.WorkspaceLinuxGID) + cmd := runAsGitpodUser(exec.Command("git", "fetch", "--unshallow", "--tags")) cmd.Dir = repoRoot cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr @@ -508,8 +504,8 @@ func Run(options ...RunOption) { wg.Wait() } -func isShallowRepository(rootDir string, uid, gid uint32) bool { - cmd := runAsUser(exec.Command("git", "rev-parse", "--is-shallow-repository"), uid, gid) +func isShallowRepository(rootDir string) bool { + cmd := runAsGitpodUser(exec.Command("git", "rev-parse", "--is-shallow-repository")) cmd.Dir = rootDir out, err := cmd.CombinedOutput() if err != nil { @@ -532,9 +528,7 @@ func installDotfiles(ctx context.Context, cfg *Config, tokenService *InMemoryTok return } - home := os.Getenv("HOME") - - dotfilePath := filepath.Join(home, ".dotfiles") + const dotfilePath = "/home/gitpod/.dotfiles" if _, err := os.Stat(dotfilePath); err == nil { // dotfile path exists already - nothing to do here return @@ -542,15 +536,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 - runAsUser(cmd, cfg.WorkspaceLinuxUID, cfg.WorkspaceLinuxGID) + cmd.Dir = "/home/gitpod" + runAsGitpodUser(cmd) cmd.Stdout = out cmd.Stderr = out return cmd } err := func() (err error) { - out, err := os.OpenFile(dotfilePath+".log", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644) + out, err := os.OpenFile("/home/gitpod/.dotfiles.log", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644) if err != nil { return err } @@ -602,7 +596,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, int(cfg.WorkspaceLinuxUID), int(cfg.WorkspaceLinuxGID)) + err = os.Chown(name, gitpodUID, gitpodGID) } return err }) @@ -664,7 +658,7 @@ func installDotfiles(ctx context.Context, cfg *Config, tokenService *InMemoryTok return nil } - homeFN := filepath.Join(os.Getenv("HOME"), strings.TrimPrefix(path, dotfilePath)) + homeFN := filepath.Join("/home/gitpod", strings.TrimPrefix(path, dotfilePath)) if _, err := os.Stat(homeFN); err == nil { // homeFN exists already - do nothing return nil @@ -748,7 +742,7 @@ func configureGit(cfg *Config) { for _, s := range settings { cmd := exec.Command("git", append([]string{"config", "--global"}, s...)...) - cmd = runAsUser(cmd, cfg.WorkspaceLinuxUID, cfg.WorkspaceLinuxGID) + cmd = runAsGitpodUser(cmd) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr err := cmd.Run() @@ -933,7 +927,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. - runAsUser(cmd, cfg.WorkspaceLinuxUID, cfg.WorkspaceLinuxGID) + runAsGitpodUser(cmd) // We need the child process to run in its own process group, s.t. we can suspend and resume // IDE and its children. @@ -1031,10 +1025,8 @@ 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 // - if cfg.WorkspaceRuntime != WorkspaceRuntimeNextgen { - envs["HOME"] = "/home/gitpod" - envs["USER"] = "gitpod" - } + 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 { @@ -1168,7 +1160,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 := io.ReadAll(resp.Body) + body, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err } @@ -1491,7 +1483,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 := os.WriteFile("/dev/termination-log", msg, 0o644) + err := ioutil.WriteFile("/dev/termination-log", msg, 0o644) if err != nil { log.WithError(err).Error("err while writing termination log") } @@ -1542,15 +1534,14 @@ func startContentInit(ctx context.Context, cfg *Config, wg *sync.WaitGroup, cst fn := "/workspace/.gitpod/content.json" fnReady := "/workspace/.gitpod/ready" - 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) + 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) // 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. @@ -1582,23 +1573,11 @@ 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 - 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) + var src csapi.WorkspaceInitSource + src, err = executor.Execute(ctx, "/workspace", contentFile, true) if err != nil { return } @@ -1826,7 +1805,7 @@ func trackReadiness(ctx context.Context, w analytics.Writer, cfg *Config, cstate } } -func runAsUser(cmd *exec.Cmd, uid, gid uint32) *exec.Cmd { +func runAsGitpodUser(cmd *exec.Cmd) *exec.Cmd { if cmd.SysProcAttr == nil { cmd.SysProcAttr = &syscall.SysProcAttr{} } @@ -1834,8 +1813,8 @@ func runAsUser(cmd *exec.Cmd, uid, gid uint32) *exec.Cmd { cmd.SysProcAttr.Credential = &syscall.Credential{} } cmd.Env = append(cmd.Env, childProcEnvvars...) - cmd.SysProcAttr.Credential.Uid = uid - cmd.SysProcAttr.Credential.Gid = gid + cmd.SysProcAttr.Credential.Uid = gitpodUID + cmd.SysProcAttr.Credential.Gid = gitpodGID return cmd } diff --git a/components/supervisor/pkg/supervisor/user.go b/components/supervisor/pkg/supervisor/user.go index fbfb9e8863d536..587d73c0c26d95 100644 --- a/components/supervisor/pkg/supervisor/user.go +++ b/components/supervisor/pkg/supervisor/user.go @@ -35,33 +35,26 @@ 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(legacyGitpodGroupName, legacyGitpodGID) + ok, err := hasGroup(gitpodGroupName, gitpodGID) if err != nil { return err } if !ok { - err = addGroup(legacyGitpodGroupName, legacyGitpodGID) + err = addGroup(gitpodGroupName, gitpodGID) if err != nil { return err } } - if err := addSudoer(legacyGitpodGroupName); err != nil { + if err := addSudoer(gitpodGroupName); err != nil { log.WithError(err).Error("add gitpod sudoers") } targetUser := &user.User{ - Uid: strconv.Itoa(legacyGitpodUID), - Gid: strconv.Itoa(legacyGitpodGID), - Username: legacyGitpodUserName, - HomeDir: "/home/" + legacyGitpodUserName, + Uid: strconv.Itoa(gitpodUID), + Gid: strconv.Itoa(gitpodGID), + Username: gitpodUserName, + HomeDir: "/home/" + gitpodUserName, } ok, err = hasUser(targetUser) if err != nil { @@ -100,7 +93,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, gid) + return true, xerrors.Errorf("group named %s exists but uses different GID %s, should be: %d", name, grpByName.Gid, gitpodGID) } // group exists and all is well @@ -134,7 +127,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: %s", u.Username, userByName.Uid, u.Uid) + return true, xerrors.Errorf("user named %s exists but uses different UID %s, should be: %d", u.Username, userByName.Uid, gitpodUID) } // 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 772a38824d5e07..30f0151a05a4c8 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: legacyGitpodUserName, Uid: strconv.Itoa(legacyGitpodUID), HomeDir: "/home/gitpod", Gid: strconv.Itoa(legacyGitpodGID)} + gitpodUser := user.User{Username: gitpodUserName, Uid: strconv.Itoa(gitpodUID), HomeDir: "/home/gitpod", Gid: strconv.Itoa(gitpodGID)} 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(legacyGitpodUserName)}, - RLookupId: opsResult{Err: user.UnknownUserIdError(legacyGitpodUID)}, + RLookup: opsResult{Err: user.UnknownUserError(gitpodUserName)}, + RLookupId: opsResult{Err: user.UnknownUserIdError(gitpodUID)}, }, }, { Name: "different UID", Ops: ops{ RLookup: opsResult{User: mod(gitpodUser, func(u *user.User) { u.Uid = "1000" })}, - RLookupId: opsResult{Err: user.UnknownUserIdError(legacyGitpodUID)}, + RLookupId: opsResult{Err: user.UnknownUserIdError(gitpodUID)}, }, Expectation: Expectation{ Exists: true, @@ -57,7 +57,7 @@ func TestHasUser(t *testing.T) { { Name: "different name", Ops: ops{ - RLookup: opsResult{Err: user.UnknownUserError(legacyGitpodUserName)}, + RLookup: opsResult{Err: user.UnknownUserError(gitpodUserName)}, 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: legacyGitpodGroupName, Gid: strconv.Itoa(legacyGitpodGID)} + gitpodGroup := user.Group{Name: gitpodGroupName, Gid: strconv.Itoa(gitpodGID)} 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(legacyGitpodGroupName)}, + RLookupGroup: opsResult{Err: user.UnknownGroupError(gitpodGroupName)}, 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(legacyGitpodGroupName)}, + RLookupGroup: opsResult{Err: user.UnknownGroupError(gitpodGroupName)}, 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(legacyGitpodGroupName, legacyGitpodGID) + exists, err := hasGroup(gitpodGroupName, gitpodGID) 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 844b7867e6c6ab..772cd14a8d2278 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{}) + initializer, err := wsinit.NewFromRequest(ctx, dst, rs, &req, wsinit.NewFromRequestOpts{ForceGitpodUserForGit: false}) if err != nil { return err }