Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions e2e/tests/ide/browser_returns.go
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,44 @@ var _ = ginkgo.Describe(
},
)

ginkgo.It(
"leaves ~/.local writable by a non-root remoteUser after code-server settings install",
ginkgo.SpecTimeout(framework.TimeoutLong()),
func(ctx context.Context) {
f := framework.NewDefaultFramework(initialDir + "/bin")
tempDir, err := framework.CopyToTempDir("tests/ide/testdata-codeserver-nonroot")
framework.ExpectNoError(err)
ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir)

err = f.DevsyProviderAdd(ctx, "docker")
framework.ExpectNoError(err)
err = f.DevsyProviderUse(ctx, "docker")
framework.ExpectNoError(err)
ginkgo.DeferCleanup(func(cleanupCtx context.Context) {
_ = f.DevsyWorkspaceDelete(cleanupCtx, tempDir)
})

err = f.DevsyUpWithIDE(ctx,
"--ide=code-server", "--ide-launch=headless", tempDir)
framework.ExpectNoError(err)

for _, dir := range []string{"$HOME/.local", "$HOME/.local/share"} {
owner, err := f.DevsySSH(ctx, tempDir, "stat -c %U "+dir)
framework.ExpectNoError(err)
gomega.Expect(strings.TrimSpace(owner)).To(gomega.Equal("devsyuser"),
"%s should be owned by the remote user, not root", dir)
}

out, err := f.DevsySSH(ctx, tempDir,
"mkdir -p $HOME/.local/lib && echo ok")
framework.ExpectNoError(err)
gomega.Expect(strings.TrimSpace(out)).To(gomega.Equal("ok"),
"remote user should be able to create ~/.local/lib")

framework.ExpectNoError(f.DevsyStop(ctx, tempDir))
},
)

ginkgo.It(
"does not log 'setup KubeConfig' on a workspace without kubeconfig forwarding",
ginkgo.SpecTimeout(framework.TimeoutLong()),
Expand Down
14 changes: 14 additions & 0 deletions e2e/tests/ide/testdata-codeserver-nonroot/.devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "code-server non-root",
"build": {
"dockerfile": "Dockerfile"
},
"remoteUser": "devsyuser",
"customizations": {
"vscode": {
"settings": {
"editor.tabSize": 2
}
}
}
}
3 changes: 3 additions & 0 deletions e2e/tests/ide/testdata-codeserver-nonroot/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
FROM ghcr.io/devsy-org/test-images/base:ubuntu

RUN useradd --create-home --shell /bin/bash devsyuser
23 changes: 23 additions & 0 deletions pkg/copy/copy.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,29 @@ func ChownR(path string, userName string) error {
})
}

func MkdirAllChown(path string, perm os.FileMode, userName string) error {
var created []string
for cur := filepath.Clean(path); !Exists(cur); {
created = append(created, cur)
parent := filepath.Dir(cur)
if parent == cur {
break
}
cur = parent
}

if err := os.MkdirAll(path, perm); err != nil {
return err
}

for _, dir := range created {
if err := Chown(dir, userName); err != nil {
return err
}
}
return nil
}

func RenameDirectory(srcDir, dest string) error {
err := Directory(srcDir, dest)
if err != nil {
Expand Down
77 changes: 77 additions & 0 deletions pkg/copy/copy_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
//go:build linux || darwin || unix

package copy

import (
"os"
"os/user"
"path/filepath"
"syscall"
"testing"
)

func currentUserName(t *testing.T) string {
t.Helper()
u, err := user.Current()
if err != nil {
t.Fatalf("current user: %v", err)
}
return u.Username
}

func ownerUID(t *testing.T, path string) uint32 {
t.Helper()
info, err := os.Stat(path)
if err != nil {
t.Fatalf("stat %s: %v", path, err)
}
stat, ok := info.Sys().(*syscall.Stat_t)
if !ok {
t.Fatalf("no syscall stat for %s", path)
}
return stat.Uid
}

func TestMkdirAllChownCreatesAndOwnsNewDirs(t *testing.T) {
root := t.TempDir()
target := filepath.Join(root, "a", "b", "c")

if err := MkdirAllChown(target, 0o755, currentUserName(t)); err != nil {
t.Fatalf("MkdirAllChown: %v", err)
}

self := os.Getuid()
for _, dir := range []string{
filepath.Join(root, "a"),
filepath.Join(root, "a", "b"),
target,
} {
if got := int(ownerUID(t, dir)); got != self {
t.Errorf("dir %s owned by uid %d, want %d", dir, got, self)
}
}
}

func TestMkdirAllChownIdempotentOnExisting(t *testing.T) {
root := t.TempDir()
target := filepath.Join(root, "x", "y")

if err := MkdirAllChown(target, 0o755, currentUserName(t)); err != nil {
t.Fatalf("first call: %v", err)
}
if err := MkdirAllChown(target, 0o755, currentUserName(t)); err != nil {
t.Fatalf("second call on existing path: %v", err)
}
}

func TestMkdirAllChownEmptyUserSkipsChown(t *testing.T) {
root := t.TempDir()
target := filepath.Join(root, "p", "q")

if err := MkdirAllChown(target, 0o755, ""); err != nil {
t.Fatalf("MkdirAllChown empty user: %v", err)
}
if !Exists(target) {
t.Fatalf("expected %s to be created", target)
}
}
3 changes: 1 addition & 2 deletions pkg/ide/codeserver/codeserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,8 +253,7 @@ func (c *CodeServer) installSettings() error {
}
codeServerDataDir := filepath.Join(homeFolder, ".local", "share", "code-server")
settingsDir := filepath.Join(codeServerDataDir, "User")
// #nosec G301 -- match openvscode-server convention for parity.
if err := os.MkdirAll(settingsDir, 0o755); err != nil {
if err := copy2.MkdirAllChown(settingsDir, 0o755, c.userName); err != nil {
return err
}
if err := os.WriteFile(
Expand Down
6 changes: 1 addition & 5 deletions pkg/ide/rstudio/rstudio.go
Original file line number Diff line number Diff line change
Expand Up @@ -319,11 +319,7 @@ func setupPreferences(workspaceFolder, userName string) error {
return fmt.Errorf("get home dir")
}
prefsDir := filepath.Join(homeDir, ".config", "rstudio")
err = os.MkdirAll(prefsDir, os.ModePerm)
if err != nil {
return err
}
err = copypkg.ChownR(prefsDir, userName)
err = copypkg.MkdirAllChown(prefsDir, os.ModePerm, userName)
if err != nil {
return err
}
Expand Down
Loading