From 396a921b1265b5a38dd3293c67139afc5a750510 Mon Sep 17 00:00:00 2001 From: Dmitry Salakhov Date: Mon, 13 Sep 2021 11:11:22 +1200 Subject: [PATCH] fix(stacks): allow root based compose file paths (#5564) --- api/exec/compose_stack.go | 35 +++++++++++++++++++++++++++++----- api/exec/compose_stack_test.go | 18 +++++++++++++++++ 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/api/exec/compose_stack.go b/api/exec/compose_stack.go index 087274fe9..c3296e37d 100644 --- a/api/exec/compose_stack.go +++ b/api/exec/compose_stack.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "path" + "path/filepath" "regexp" "strings" @@ -58,8 +59,8 @@ func (manager *ComposeStackManager) Up(ctx context.Context, stack *portainer.Sta return errors.Wrap(err, "failed to create env file") } - filePaths := append([]string{stack.EntryPoint}, stack.AdditionalFiles...) - return manager.deployer.Deploy(ctx, stack.ProjectPath, url, stack.Name, filePaths, envFilePath) + filePaths := getStackFiles(stack) + err = manager.deployer.Deploy(ctx, stack.ProjectPath, url, stack.Name, filePaths, envFilePath) return errors.Wrap(err, "failed to deploy a stack") } @@ -73,9 +74,9 @@ func (manager *ComposeStackManager) Down(ctx context.Context, stack *portainer.S defer proxy.Close() } - filePaths := append([]string{stack.EntryPoint}, stack.AdditionalFiles...) - - return manager.deployer.Remove(ctx, stack.ProjectPath, url, stack.Name, filePaths) + filePaths := getStackFiles(stack) + err = manager.deployer.Remove(ctx, stack.ProjectPath, url, stack.Name, filePaths) + return errors.Wrap(err, "failed to remove a stack") } // NormalizeStackName returns a new stack name with unsupported characters replaced @@ -116,3 +117,27 @@ func createEnvFile(stack *portainer.Stack) (string, error) { return "stack.env", nil } + +// getStackFiles returns list of stack's confile file paths. +// items in the list would be sanitized according to following criterias: +// 1. no empty paths +// 2. no "../xxx" paths that are trying to escape stack folder +// 3. no dir paths +// 4. root paths would be made relative +func getStackFiles(stack *portainer.Stack) []string { + paths := make([]string, 0, len(stack.AdditionalFiles)+1) + + for _, p := range append([]string{stack.EntryPoint}, stack.AdditionalFiles...) { + if strings.HasPrefix(p, "/") { + p = `.` + p + } + + if p == `` || p == `.` || strings.HasPrefix(p, `..`) || strings.HasSuffix(p, string(filepath.Separator)) { + continue + } + + paths = append(paths, p) + } + + return paths +} diff --git a/api/exec/compose_stack_test.go b/api/exec/compose_stack_test.go index c61285ebd..0b5dec2a3 100644 --- a/api/exec/compose_stack_test.go +++ b/api/exec/compose_stack_test.go @@ -64,3 +64,21 @@ func Test_createEnvFile(t *testing.T) { }) } } + +func Test_getStackFiles(t *testing.T) { + stack := &portainer.Stack{ + EntryPoint: "./file", // picks entry point + AdditionalFiles: []string{ + ``, // ignores empty string + `.`, // ignores . + `..`, // ignores .. + `./dir/`, // ignrores paths that end with trailing / + `/with-root-prefix`, // replaces "root" based paths with relative + `./relative`, // keeps relative paths + `../escape`, // prevents dir escape + }, + } + + filePaths := getStackFiles(stack) + assert.ElementsMatch(t, filePaths, []string{`./file`, `./with-root-prefix`, `./relative`}) +}