diff --git a/cmd/launcher/cli/launcher.go b/cmd/launcher/cli/launcher.go index 82e09e02e..db977a67e 100644 --- a/cmd/launcher/cli/launcher.go +++ b/cmd/launcher/cli/launcher.go @@ -49,6 +49,7 @@ func RunLaunch() error { ExecD: launch.NewExecDRunner(), Shell: launch.DefaultShell, Setenv: os.Setenv, + Logger: cmd.DefaultLogger, } if err := launcher.Launch(os.Args[0], os.Args[1:]); err != nil { diff --git a/launch/launcher.go b/launch/launcher.go index e828757b3..e50838b0b 100644 --- a/launch/launcher.go +++ b/launch/launcher.go @@ -9,6 +9,7 @@ import ( "github.com/buildpacks/lifecycle/api" "github.com/buildpacks/lifecycle/env" + "github.com/buildpacks/lifecycle/log" ) var ( @@ -26,9 +27,11 @@ type Launcher struct { ExecD ExecD Shell Shell LayersDir string + Logger *log.DefaultLogger PlatformAPI *api.Version Processes []Process Setenv func(string, string) error + Setumask func(Env) error } type ExecFunc func(argv0 string, argv []string, envv []string) error @@ -69,6 +72,14 @@ func (l *Launcher) LaunchProcess(self string, proc Process) error { } proc.WorkingDirectory = getProcessWorkingDirectory(proc, l.AppDir) + if l.Setumask == nil { + l.Setumask = SetUmask + } + err := l.Setumask(l.Env) + if err != nil && l.Logger != nil { + l.Logger.Errorf("invalid umask: %s", err) + } + if proc.Direct { return l.launchDirect(proc) } diff --git a/launch/launcher_test.go b/launch/launcher_test.go index 92c3d8476..33e3e8c16 100644 --- a/launch/launcher_test.go +++ b/launch/launcher_test.go @@ -124,6 +124,9 @@ func testLauncher(t *testing.T, when spec.G, it spec.S) { // set command to something on the real path so exec.LookPath succeeds process.Command = launch.NewRawCommand([]string{"sh"}) + launcher.Setumask = func(_ launch.Env) error { + return nil + } mockEnv.EXPECT().Get("PATH").Return("some-path").AnyTimes() launcher.Setenv = func(k string, v string) error { if k == "PATH" { @@ -373,6 +376,9 @@ func testLauncher(t *testing.T, when spec.G, it spec.S) { it.Before(func() { shell = &fakeShell{} launcher.Shell = shell + launcher.Setumask = func(_ launch.Env) error { + return nil + } }) it("sets Caller to self", func() { diff --git a/launch/umask_unix.go b/launch/umask_unix.go new file mode 100644 index 000000000..284ca9c94 --- /dev/null +++ b/launch/umask_unix.go @@ -0,0 +1,35 @@ +//go:build unix + +package launch + +import ( + "fmt" + "strconv" + "syscall" + + "github.com/pkg/errors" +) + +const ( + umaskEnvVar = "CNB_LAUNCH_UMASK" +) + +// SetUmask on unix systems from the value in the `UMASK` environment variable +func SetUmask(env Env) error { + return SetUmaskWith(env, syscall.Umask) +} + +// SetUmaskWith the injected function umaskFn +func SetUmaskWith(env Env, umaskFn func(int) int) error { + umask := env.Get(umaskEnvVar) + if umask == "" { + return nil + } + + u, err := strconv.ParseInt(umask, 8, 0) + if err != nil { + return errors.Wrap(err, fmt.Sprintf("invalid umask value %s", umask)) + } + umaskFn(int(u)) + return nil +} diff --git a/launch/umask_unix_test.go b/launch/umask_unix_test.go new file mode 100644 index 000000000..02cfacfb6 --- /dev/null +++ b/launch/umask_unix_test.go @@ -0,0 +1,77 @@ +//go:build unix + +package launch_test + +import ( + "testing" + + "github.com/sclevine/spec" + "github.com/sclevine/spec/report" + + "github.com/buildpacks/lifecycle/env" + "github.com/buildpacks/lifecycle/launch" + h "github.com/buildpacks/lifecycle/testhelpers" +) + +func TestUmask(t *testing.T) { + spec.Run(t, "Umask", testUmask, spec.Report(report.Terminal{})) +} + +func testUmask(t *testing.T, when spec.G, it spec.S) { + when("UMASK is set", func() { + it("parses octal umask values", func() { + tests := []struct { + name string + umask string + expected int + }{ + {"standard user umask", "0002", 2}, + {"restrictive umask", "0077", 63}, + {"permissive umask", "0000", 0}, + {"three digit umask", "022", 18}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + environ := env.NewLaunchEnv([]string{"CNB_LAUNCH_UMASK=" + tt.umask}, "", "") + + var called int + spy := func(m int) int { + called = m + return 0 + } + + err := launch.SetUmaskWith(environ, spy) + + h.AssertNil(t, err) + h.AssertEq(t, called, tt.expected) + }) + } + }) + + it("returns error for invalid umask", func() { + environ := env.NewLaunchEnv([]string{"CNB_LAUNCH_UMASK=invalid"}, "", "") + + err := launch.SetUmaskWith(environ, func(int) int { return 0 }) + + h.AssertNotNil(t, err) + }) + }) + + when("UMASK is unset", func() { + it("does not call umask function", func() { + environ := env.NewLaunchEnv([]string{}, "", "") + + called := false + spy := func(_ int) int { + called = true + return 0 + } + + err := launch.SetUmaskWith(environ, spy) + + h.AssertNil(t, err) + h.AssertEq(t, called, false) + }) + }) +} diff --git a/launch/umask_windows.go b/launch/umask_windows.go new file mode 100644 index 000000000..aeeb915f6 --- /dev/null +++ b/launch/umask_windows.go @@ -0,0 +1,5 @@ +package launch + +func SetUmask(env Env) { + // no operation +}