grim/convey

Parents 6a763f8540d1
Children 3702d744bdb6
Correctly honor the build context. That is, don't allow import/export of files higher in the directory tree than the convey.yaml
--- a/.hgignore Wed Nov 22 11:48:51 2017 -0600
+++ b/.hgignore Wed Dec 27 01:06:03 2017 -0600
@@ -1,6 +1,7 @@
syntax: regexp
^convey(-.+-amd64(\.exe)?)?$
\/junit\.xml$
+\/(foo|bar|baz|qux|quux)\/
syntax: glob
*.sublime-workspace
--- a/ChangeLog Wed Nov 22 11:48:51 2017 -0600
+++ b/ChangeLog Wed Dec 27 01:06:03 2017 -0600
@@ -3,6 +3,7 @@
* Added --list-environment to show the config and builtin environment variables. Fixed #132
* Added a user option to the Docker run task. PR #38 (Eric Fritz)
* Added a target option to the Docker build task. To specify which stage to use in a multi stage Dockerfile.
+ * Correctly honor the build context.
0.11.1: 20171021
* Fix a regression in the environment mapper that could mess up script attributes. PR #37 (Eric Fritz)
--- a/docker/export.go Wed Nov 22 11:48:51 2017 -0600
+++ b/docker/export.go Wed Dec 27 01:06:03 2017 -0600
@@ -26,6 +26,7 @@
"github.com/aphistic/gomol"
"bitbucket.org/rw_grim/convey/environment"
+ "bitbucket.org/rw_grim/convey/path"
"bitbucket.org/rw_grim/convey/state"
"bitbucket.org/rw_grim/convey/tasks"
"bitbucket.org/rw_grim/convey/yaml"
@@ -110,7 +111,13 @@
} else {
src, dest := tasks.ParseFilePath("", file)
- if err := exportFile(name, st.Workspace.Name(), src, dest, st); err != nil {
+ // make sure the destination is in our build context
+ realDest, err := path.TraversesNonExistent(st.CfgPath, dest)
+ if err != nil {
+ return err
+ }
+
+ if err := exportFile(name, st.Workspace.Name(), src, realDest, st); err != nil {
return err
}
}
--- a/docker/import.go Wed Nov 22 11:48:51 2017 -0600
+++ b/docker/import.go Wed Dec 27 01:06:03 2017 -0600
@@ -21,6 +21,7 @@
"github.com/aphistic/gomol"
"bitbucket.org/rw_grim/convey/environment"
+ "bitbucket.org/rw_grim/convey/path"
"bitbucket.org/rw_grim/convey/state"
"bitbucket.org/rw_grim/convey/tasks"
"bitbucket.org/rw_grim/convey/yaml"
@@ -48,8 +49,14 @@
src, dest := tasks.ParseFilePath("", file)
+ // make sure the file we're import is in our build context
+ realSrc, err := path.Traverses(st.CfgPath, src)
+ if err != nil {
+ return err
+ }
+
params := map[string]interface{}{
- "source": src,
+ "source": realSrc,
"destination": dest,
"workspaceID": st.Workspace.Name(),
}
--- a/main.go Wed Nov 22 11:48:51 2017 -0600
+++ b/main.go Wed Dec 27 01:06:03 2017 -0600
@@ -20,6 +20,7 @@
import (
"fmt"
"os"
+ "path/filepath"
"strings"
"github.com/alecthomas/kingpin"
@@ -98,19 +99,30 @@
// now make sure we found a config file
if *configFile == "" {
fmt.Printf("config file not found, looking for %s\n", strings.Join(loader.Filenames(), ","))
- os.Exit(1)
+ exitCode = 1
+ return
+ }
+
+ // figure out the path to the config file
+ cfgPath, err := filepath.Abs(filepath.Dir(*configFile))
+ if err != nil {
+ fmt.Printf("%#v\n", err)
+ exitCode = 1
+ return
}
cfg, err := config.LoadFile(*configFile, loader)
if err != nil {
fmt.Printf("%s\n", err)
- os.Exit(1)
+ exitCode = 1
+ return
}
// setup logging
if err := logging.Setup(*color, *verbose); err != nil {
fmt.Printf("failed to setup logging: %s\n", err)
- os.Exit(1)
+ exitCode = 1
+ return
}
defer gomol.ShutdownLoggers()
@@ -136,6 +148,7 @@
cleanupList := cleanup.NewList()
st := state.New()
+ st.CfgPath = cfgPath
st.CleanupList = cleanupList
st.KeepWorkspace = *keep
st.ForceSequential = *forceSequential
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/path/path.go Wed Dec 27 01:06:03 2017 -0600
@@ -0,0 +1,97 @@
+/*
+ * Convey
+ * Copyright 2016-2017 Gary Kramlich <grim@reaperworld.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package path
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+ "strings"
+)
+
+// Traverses check whether path traverses outside of root. Returns error if
+// root is traversed.
+func Traverses(root, path string) (string, error) {
+ if !filepath.IsAbs(path) {
+ path = filepath.Join(root, path)
+ }
+
+ realPath, err := filepath.EvalSymlinks(path)
+ if err != nil {
+ return "", err
+ }
+
+ if !strings.HasPrefix(realPath, root) {
+ return "", fmt.Errorf("'%s' is outside of the build context '%s'", path, root)
+ }
+
+ return realPath, nil
+}
+
+func TraversesNonExistent(root, path string) (string, error) {
+ // attempt to do a normal traversal
+ realPath, err := Traverses(root, path)
+
+ // if it failed with a os.PathError that means the path doesn't exist.
+ // so we will strip off the filename and walk up to see if we can still
+ // find a match.
+ if _, ok := err.(*os.PathError); ok {
+ // reset realPath as the original call to Traverses will return an
+ // empty string for it on error
+ realPath = path
+
+ // create a variable to hold the path that we've removed
+ stripped := ""
+
+ i := 0
+
+ for {
+ // split the directory and file name, we only hit this on error,
+ // so we can't actually eval symlinks completely, so it's safe-ish
+ // to split.
+ dir, file := filepath.Split(realPath)
+ if file == "" {
+ dir = filepath.Dir(dir)
+ file = filepath.Base(dir)
+ }
+
+ // join the filename to the beginning for stripped as we're walking
+ // up the directory tree.
+ stripped = filepath.Join(file, stripped)
+
+ // call Traverses again
+ realPath, err = Traverses(root, dir)
+
+ // if there was no error, we're done
+ if err == nil {
+ return filepath.Join(realPath, stripped), nil
+ } else {
+ realPath = dir
+ }
+
+ i++
+ if i >= 10 {
+ break
+ }
+ }
+ } else if err != nil {
+ return "", err
+ }
+
+ return realPath, nil
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/path/path_test.go Wed Dec 27 01:06:03 2017 -0600
@@ -0,0 +1,64 @@
+/*
+ * Convey
+ * Copyright 2016-2017 Gary Kramlich <grim@reaperworld.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package path
+
+import (
+ "os"
+ "path/filepath"
+ "testing"
+
+ "github.com/aphistic/sweet"
+ junit "github.com/aphistic/sweet-junit"
+ . "github.com/onsi/gomega"
+)
+
+type pathSuite struct{}
+
+func TestMain(m *testing.M) {
+ RegisterFailHandler(sweet.GomegaFail)
+
+ sweet.Run(m, func(s *sweet.S) {
+ s.RegisterPlugin(junit.NewPlugin())
+
+ s.AddSuite(&pathSuite{})
+ })
+}
+
+func (s *pathSuite) TestTraversed(t sweet.T) {
+ tests := map[string]bool{
+ "/": true,
+ "..": true,
+ ".": false,
+ "bar": false,
+ "foo/../baz": true,
+ }
+
+ wd, err := os.Getwd()
+ Expect(err).To(BeNil())
+
+ wd = filepath.Join(wd, "foo")
+
+ for path, res := range tests {
+ _, err := Traverses(wd, path)
+ if res {
+ Expect(err).ToNot(BeNil())
+ } else {
+ Expect(err).To(BeNil())
+ }
+ }
+}
--- a/state/state.go Wed Nov 22 11:48:51 2017 -0600
+++ b/state/state.go Wed Dec 27 01:06:03 2017 -0600
@@ -33,6 +33,7 @@
const ExpansionLimit = 100
type State struct {
+ CfgPath string
CleanupList *cleanup.List
Network network.Network
Workspace workspace.Workspace
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/breakout.yml Wed Dec 27 01:06:03 2017 -0600
@@ -0,0 +1,28 @@
+tasks:
+ import-bad:
+ type: import
+ files: /etc/ssl:/ssl
+ import:
+ type: import
+ files: breakout.yml
+ export-bad:
+ type: export
+ files: breakout.yml:/
+ export:
+ type: export
+ files: breakout.yml:qux/quux/free.yml
+plans:
+ default:
+ stages:
+ - tasks:
+ - import
+ - export
+ import-bad:
+ stages:
+ - tasks:
+ - import-bad
+ export-bad:
+ stages:
+ - tasks:
+ - import
+ - export-bad
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/free.yml Wed Dec 27 01:06:03 2017 -0600
@@ -0,0 +1,20 @@
+tasks:
+ import-bad:
+ type: import
+ files: /etc/ssl:/ssl
+ import:
+ type: import
+ files: breakout.yml
+ export-bad:
+ type: export
+ files: breakout.yml:/
+ export:
+ type: export
+ files: breakout.yml:free.yml
+plans:
+ default:
+ stages:
+ - tasks:
+ - import
+ - export
+