diff --git a/cmd/sciontool/commands/init.go b/cmd/sciontool/commands/init.go index 302f67e8..bebcfe6e 100644 --- a/cmd/sciontool/commands/init.go +++ b/cmd/sciontool/commands/init.go @@ -1002,14 +1002,8 @@ func gitCloneWorkspace(uid, gid int, agentHome string) error { } } - // Ensure the workspace directory is owned by the target user when running - // as root. Non-root containers cannot chown, and Kubernetes pods may - // already be running as the correct user/group via securityContext. - if uid > 0 && os.Getuid() == 0 { - if err := os.Chown(workspacePath, uid, gid); err != nil { - log.Error("Failed to chown workspace to UID=%d GID=%d: %v", uid, gid, err) - } - } + currentEUID := os.Geteuid() + ensureWorkspaceOwnership(workspacePath, uid, gid, currentEUID, os.Chown) token := os.Getenv("GITHUB_TOKEN") branch := os.Getenv("SCION_GIT_BRANCH") @@ -1223,6 +1217,22 @@ func gitCloneWorkspace(uid, gid int, agentHome string) error { return nil } +func ensureWorkspaceOwnership(workspacePath string, uid, gid, currentEUID int, chown func(string, int, int) error) { + // Only root can successfully chown a mounted workspace. In restricted + // Kubernetes pods the init process may already be running as the scion + // user, so attempting chown here just adds noise before git init. + if uid <= 0 { + return + } + if currentEUID != 0 { + log.Info("Skipping workspace chown for %s; running as non-root UID=%d", workspacePath, currentEUID) + return + } + if err := chown(workspacePath, uid, gid); err != nil { + log.Error("Failed to chown workspace to UID=%d GID=%d: %v", uid, gid, err) + } +} + // configureSharedWorkspaceGit sets up git credentials for shared-workspace // (git-workspace hybrid) groves. The workspace is a pre-cloned git repo shared // by all agents; each agent gets its own credential helper in $HOME/.gitconfig diff --git a/cmd/sciontool/commands/init_test.go b/cmd/sciontool/commands/init_test.go index 22f54e89..5b87bcac 100644 --- a/cmd/sciontool/commands/init_test.go +++ b/cmd/sciontool/commands/init_test.go @@ -745,7 +745,6 @@ func TestGitCloneWorkspace_NonZeroUIDChownsWorkspace(t *testing.T) { t.Errorf("expected a git failure error, got: %v", err) } } - func TestConfigureGitCommand_SkipsCredentialOverrideWhenAlreadyRunningAsTargetUser(t *testing.T) { cmd := exec.CommandContext(context.Background(), "git", "status") @@ -774,3 +773,34 @@ func TestConfigureGitCommand_SkipsCredentialOverrideForNonRootDifferentTarget(t t.Fatalf("expected no credential override for non-root process, got %#v", cmd.SysProcAttr) } } + +func TestEnsureWorkspaceOwnership_SkipsChownWhenNonRoot(t *testing.T) { + chownCalled := false + chown := func(string, int, int) error { + chownCalled = true + return nil + } + + ensureWorkspaceOwnership("/workspace", 1000, 1000, 1000, chown) + + if chownCalled { + t.Fatal("expected chown to be skipped when already running as non-root") + } +} + +func TestEnsureWorkspaceOwnership_ChownsWhenRoot(t *testing.T) { + var gotPath string + var gotUID, gotGID int + chown := func(path string, uid, gid int) error { + gotPath = path + gotUID = uid + gotGID = gid + return nil + } + + ensureWorkspaceOwnership("/workspace", 1000, 1000, 0, chown) + + if gotPath != "/workspace" || gotUID != 1000 || gotGID != 1000 { + t.Fatalf("unexpected chown call: path=%q uid=%d gid=%d", gotPath, gotUID, gotGID) + } +}