Skip to content

Commit

Permalink
WorkingDir inherit permissions of parent
Browse files Browse the repository at this point in the history
Prior to this PR, workingDirInit did not deal handle permissions of
nested child directories in workingDirs. This PR provides the same
permissions as that of the existing parent directory. This allows
non-root users to also create relative sub diectories in a workspace
if used as a workingDir.
Fixes #7804.
  • Loading branch information
chitrangpatel committed May 27, 2024
1 parent 1f3b1b4 commit 5993c98
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 40 deletions.
78 changes: 71 additions & 7 deletions cmd/workingdirinit/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,27 +17,91 @@ limitations under the License.
package main

import (
"fmt"
"io/fs"
"log"
"os"
"path/filepath"
"runtime"
"strings"
)

// exists returns whether the given file or directory exists
func exists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
}

func getPermissions(path string) (fs.FileMode, error) {
fileInfo, err := os.Stat(path)
if err != nil {
return fs.FileMode(0o666), err
}
return fileInfo.Mode(), nil
}

func createPath(path string) error {
// Check if the path already exists.
ex, err := exists(path)
if err != nil {
return fmt.Errorf("error accessing path %s: %w", path, err)
}
// Path already exists, so no subdirectory to create. Skip the rest.
if ex {
return nil
}
// Find the innermost parent directory to get the permissions to apply to the child directories.
parts := strings.Split(path, string(filepath.Separator))
parent := path
i := 0
// If we reach the file system root then we could not find any existing Dir.
// In this case, workingDir Init cannot handle it. Let k8s handle the creation instead.
for parent != "/" {
parent = filepath.Dir(parent)
if ex, err := exists(parent); err != nil {
return fmt.Errorf("error accessing path %s: %w", parent, err)
} else if ex {
// We need to get its permissions and apply it to the child directories.
perm, err := getPermissions(parent)
if err != nil {
return fmt.Errorf("error accessing path %s: %w", parent, err)
}
// Create the path
if err := os.MkdirAll(path, perm); err != nil {
return fmt.Errorf("failed to mkdir %q: %w", path, err)
}
// walk up again and set the permissions of the parent to the child directories
d := parent
idx := len(parts) - i - 1
for j := idx; j < len(parts); j++ {
d = filepath.Join(d, parts[j])
if err := os.Chmod(d, perm); err != nil {
return fmt.Errorf("failed to chmod %q: %w", d, err)
}
}
return nil
}
i += 1
}
return nil
}

func main() {
for i, d := range os.Args {
// os.Args[0] is the path to this executable, so we should skip it
if i == 0 {
continue
}

ws := cleanPath("/workspace/")
p := cleanPath(d)

if !filepath.IsAbs(p) || strings.HasPrefix(p, ws+string(filepath.Separator)) {
if err := os.MkdirAll(p, 0755); err != nil {
log.Fatalf("Failed to mkdir %q: %v", p, err)
}
err := createPath(p)
if err != nil {
log.Fatalf("Failed to create path %s: %v", p, err)
}
}
}
Expand Down
1 change: 0 additions & 1 deletion pkg/pod/pod_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,6 @@ func TestPodBuild(t *testing.T) {
Image: images.WorkingDirInitImage,
Command: []string{"/ko-app/workingdirinit"},
Args: []string{filepath.Join(pipeline.WorkspaceDir, "test")},
WorkingDir: pipeline.WorkspaceDir,
VolumeMounts: implicitVolumeMounts,
},
},
Expand Down
46 changes: 28 additions & 18 deletions pkg/pod/workingdir_init.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,27 +36,38 @@ import (
// If the init container will run on windows, `windows` should be set to `true`,
// so that the correct security context can be applied.
func workingDirInit(workingdirinitImage string, stepContainers []corev1.Container, setSecurityContext, windows bool) *corev1.Container {
// Gather all unique workingDirs.
workingDirs := sets.NewString()
vms := implicitVolumeMounts
// To ensure that we don't duplicate volume mounts
requestedVolumeMounts := map[string]bool{}
for _, step := range stepContainers {
if step.WorkingDir != "" {
workingDirs.Insert(step.WorkingDir)
for _, vm := range step.VolumeMounts {
// Only add unique volume mounts from Steps
if !requestedVolumeMounts[filepath.Clean(vm.MountPath)] {
vms = append(vms, vm)
requestedVolumeMounts[filepath.Clean(vm.MountPath)] = true
}
}
}
if workingDirs.Len() == 0 {
return nil
}

// Clean and append each relative workingDir.
var relativeDirs []string
for _, wd := range workingDirs.List() {
p := filepath.Clean(wd)
if !filepath.IsAbs(p) || strings.HasPrefix(p, "/workspace/") {
relativeDirs = append(relativeDirs, p)
// Gather all unique workingDirs that are on VolumeMounts.
workingDirs := sets.New[string]()
for _, step := range stepContainers {
if step.WorkingDir != "" {
// Only handles relative path inside `/workspace` for backwards compatibility.
if !filepath.IsAbs(step.WorkingDir) {
step.WorkingDir = filepath.Join(pipeline.WorkspaceDir, step.WorkingDir)
}
for _, vm := range vms {
// only append to workingDirs if the step's workingDir is a subdir of a VolumeMount.
// Otherwise, the path will be unique to the init container and won't actually affect
// the step's workingDir. In that case, we want to let k8s handle the creation.
if strings.HasPrefix(step.WorkingDir, vm.MountPath) {
workingDirs.Insert(filepath.Clean(step.WorkingDir))
}
}
}
}

if len(relativeDirs) == 0 {
if workingDirs.Len() == 0 {
// There are no workingDirs to initialize.
return nil
}
Expand All @@ -69,9 +80,8 @@ func workingDirInit(workingdirinitImage string, stepContainers []corev1.Containe
Name: "working-dir-initializer",
Image: workingdirinitImage,
Command: []string{"/ko-app/workingdirinit"},
Args: relativeDirs,
WorkingDir: pipeline.WorkspaceDir,
VolumeMounts: implicitVolumeMounts,
Args: workingDirs.UnsortedList(),
VolumeMounts: vms,
}
if setSecurityContext {
c.SecurityContext = securityContext
Expand Down
29 changes: 15 additions & 14 deletions pkg/pod/workingdir_init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ limitations under the License.
package pod

import (
"sort"
"testing"

"github.com/google/go-cmp/cmp"
"github.com/tektoncd/pipeline/pkg/apis/pipeline"
"github.com/tektoncd/pipeline/test/diff"
"github.com/tektoncd/pipeline/test/names"
corev1 "k8s.io/api/core/v1"
Expand All @@ -41,7 +41,7 @@ func TestWorkingDirInit(t *testing.T) {
}},
want: nil,
}, {
desc: "workingDirs are unique and sorted, absolute dirs are ignored",
desc: "workingDirs are unique",
stepContainers: []corev1.Container{{
WorkingDir: "zzz",
}, {
Expand All @@ -61,12 +61,11 @@ func TestWorkingDirInit(t *testing.T) {
Name: "working-dir-initializer",
Image: images.WorkingDirInitImage,
Command: []string{"/ko-app/workingdirinit"},
Args: []string{"/workspace/bbb", "aaa", "zzz"},
WorkingDir: pipeline.WorkspaceDir,
Args: []string{"/workspace/zzz", "/workspace/aaa", "/workspace", "/workspace/bbb"},
VolumeMounts: implicitVolumeMounts,
},
}, {
desc: "workingDirs are unique and sorted, absolute dirs are ignored, + securitycontext",
desc: "workingDirs are unique + securitycontext",
stepContainers: []corev1.Container{{
WorkingDir: "zzz",
}, {
Expand All @@ -87,13 +86,12 @@ func TestWorkingDirInit(t *testing.T) {
Name: "working-dir-initializer",
Image: images.WorkingDirInitImage,
Command: []string{"/ko-app/workingdirinit"},
Args: []string{"/workspace/bbb", "aaa", "zzz"},
WorkingDir: pipeline.WorkspaceDir,
Args: []string{"/workspace/zzz", "/workspace/aaa", "/workspace", "/workspace/bbb"},
VolumeMounts: implicitVolumeMounts,
SecurityContext: linuxSecurityContext,
},
}, {
desc: "workingDirs are unique and sorted, absolute dirs are ignored, uses windows",
desc: "workingDirs are unique, uses windows",
stepContainers: []corev1.Container{{
WorkingDir: "zzz",
}, {
Expand All @@ -114,12 +112,11 @@ func TestWorkingDirInit(t *testing.T) {
Name: "working-dir-initializer",
Image: images.WorkingDirInitImage,
Command: []string{"/ko-app/workingdirinit"},
Args: []string{"/workspace/bbb", "aaa", "zzz"},
WorkingDir: pipeline.WorkspaceDir,
Args: []string{"/workspace/zzz", "/workspace/aaa", "/workspace", "/workspace/bbb"},
VolumeMounts: implicitVolumeMounts,
},
}, {
desc: "workingDirs are unique and sorted, absolute dirs are ignored, uses windows, + securityContext",
desc: "workingDirs are unique, uses windows, + securityContext",
stepContainers: []corev1.Container{{
WorkingDir: "zzz",
}, {
Expand All @@ -141,15 +138,19 @@ func TestWorkingDirInit(t *testing.T) {
Name: "working-dir-initializer",
Image: images.WorkingDirInitImage,
Command: []string{"/ko-app/workingdirinit"},
Args: []string{"/workspace/bbb", "aaa", "zzz"},
WorkingDir: pipeline.WorkspaceDir,
Args: []string{"/workspace/zzz", "/workspace/aaa", "/workspace", "/workspace/bbb"},
VolumeMounts: implicitVolumeMounts,
SecurityContext: windowsSecurityContext,
},
}} {
t.Run(c.desc, func(t *testing.T) {
got := workingDirInit(images.WorkingDirInitImage, c.stepContainers, c.setSecurityContext, c.windows)
if d := cmp.Diff(c.want, got); d != "" {
trans := cmp.Transformer("Sort", func(inp corev1.Container) []string {
out := append([]string(nil), inp.Args...) // Copy input to avoid mutating it
sort.Strings(out)
return out
})
if d := cmp.Diff(c.want, got, trans); d != "" {
t.Fatalf("Diff %s", diff.PrintWantGot(d))
}
})
Expand Down

0 comments on commit 5993c98

Please sign in to comment.