Skip to content
Snippets Groups Projects
Unverified Commit f1e79986 authored by Kamil Trzciński's avatar Kamil Trzciński Committed by Tomasz Maczukin
Browse files

Merge branch 'fix-cache-key-escape-for-11-3' into 'master'

Disable escaping project bucket in cache operations

Closes #3589

See merge request gitlab-org/gitlab-runner!1029
parent d6ef43e3
Branches
No related tags found
No related merge requests found
package cache package cache
import ( import (
"fmt"
"net/url" "net/url"
"path" "path"
"path/filepath"
"strconv" "strconv"
"strings"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
...@@ -20,17 +23,33 @@ func getCacheConfig(build *common.Build) *common.CacheConfig { ...@@ -20,17 +23,33 @@ func getCacheConfig(build *common.Build) *common.CacheConfig {
return build.Runner.Cache return build.Runner.Cache
} }
func generateObjectName(build *common.Build, config *common.CacheConfig, key string) string { func generateBaseObjectName(build *common.Build, config *common.CacheConfig) string {
if key == "" {
return ""
}
runnerSegment := "" runnerSegment := ""
if !config.GetShared() { if !config.GetShared() {
runnerSegment = path.Join("runner", build.Runner.ShortDescription()) runnerSegment = path.Join("runner", build.Runner.ShortDescription())
} }
return path.Join(config.GetPath(), runnerSegment, "project", strconv.Itoa(build.JobInfo.ProjectID), key) return path.Join(config.GetPath(), runnerSegment, "project", strconv.Itoa(build.JobInfo.ProjectID))
}
func generateObjectName(build *common.Build, config *common.CacheConfig, key string) (string, error) {
if key == "" {
return "", nil
}
basePath := generateBaseObjectName(build, config)
path := path.Join(basePath, key)
relative, err := filepath.Rel(basePath, path)
if err != nil {
return "", fmt.Errorf("cache path correctness check failed with: %v", err)
}
if strings.HasPrefix(relative, ".."+string(filepath.Separator)) {
return "", fmt.Errorf("computed cache path outside of project bucket. Please remove `../` from cache key")
}
return path, nil
} }
func onAdapter(build *common.Build, key string, handler func(adapter Adapter) *url.URL) *url.URL { func onAdapter(build *common.Build, key string, handler func(adapter Adapter) *url.URL) *url.URL {
...@@ -40,7 +59,12 @@ func onAdapter(build *common.Build, key string, handler func(adapter Adapter) *u ...@@ -40,7 +59,12 @@ func onAdapter(build *common.Build, key string, handler func(adapter Adapter) *u
return nil return nil
} }
objectName := generateObjectName(build, config, key) objectName, err := generateObjectName(build, config, key)
if err != nil {
logrus.WithError(err).Error("Error while generating cache bucket.")
return nil
}
if objectName == "" { if objectName == "" {
logrus.Warning("Empty cache key. Skipping adapter selection.") logrus.Warning("Empty cache key. Skipping adapter selection.")
return nil return nil
......
...@@ -173,63 +173,92 @@ func defaultBuild(cacheConfig *common.CacheConfig) *common.Build { ...@@ -173,63 +173,92 @@ func defaultBuild(cacheConfig *common.CacheConfig) *common.Build {
} }
} }
func TestGenerateObjectNameWhenKeyIsEmptyResultIsAlsoEmpty(t *testing.T) { type generateObjectNameTestCase struct {
cache := defaultCacheConfig() cache *common.CacheConfig
cacheBuild := defaultBuild(cache) build *common.Build
url := generateObjectName(cacheBuild, cache, "")
assert.Empty(t, url)
}
func TestGetCacheObjectName(t *testing.T) {
cache := defaultCacheConfig()
cacheBuild := defaultBuild(cache)
url := generateObjectName(cacheBuild, cache, "key")
assert.Equal(t, "runner/longtoke/project/10/key", url)
}
func TestGetCacheObjectNameWhenPathIsSetThenUrlContainsIt(t *testing.T) { key string
cache := defaultCacheConfig() path string
cache.Path = "whatever" shared bool
cacheBuild := defaultBuild(cache)
url := generateObjectName(cacheBuild, cache, "key") expectedObjectName string
assert.Equal(t, "whatever/runner/longtoke/project/10/key", url) expectedError string
} }
func TestGetCacheObjectNameWhenPathHasMultipleSegmentIsSetThenUrlContainsIt(t *testing.T) { func TestGenerateObjectName(t *testing.T) {
cache := defaultCacheConfig() cache := defaultCacheConfig()
cache.Path = "some/other/path/goes/here"
cacheBuild := defaultBuild(cache) cacheBuild := defaultBuild(cache)
url := generateObjectName(cacheBuild, cache, "key") tests := map[string]generateObjectNameTestCase{
assert.Equal(t, "some/other/path/goes/here/runner/longtoke/project/10/key", url) "default usage": {
} cache: cache,
build: cacheBuild,
func TestGetCacheObjectNameWhenPathIsNotSetThenUrlDoesNotContainIt(t *testing.T) { key: "key",
cache := defaultCacheConfig() expectedObjectName: "runner/longtoke/project/10/key",
cache.Path = "" },
cacheBuild := defaultBuild(cache) "empty key": {
cache: cache,
url := generateObjectName(cacheBuild, cache, "key") build: cacheBuild,
assert.Equal(t, "runner/longtoke/project/10/key", url) key: "",
} expectedObjectName: "",
},
func TestGetCacheObjectNameWhenSharedFlagIsFalseThenRunnerSegmentExistsInTheUrl(t *testing.T) { "short path is set": {
cache := defaultCacheConfig() cache: cache,
cache.Shared = false build: cacheBuild,
cacheBuild := defaultBuild(cache) key: "key",
path: "whatever",
expectedObjectName: "whatever/runner/longtoke/project/10/key",
},
"multiple segment path is set": {
cache: cache,
build: cacheBuild,
key: "key",
path: "some/other/path/goes/here",
expectedObjectName: "some/other/path/goes/here/runner/longtoke/project/10/key",
},
"path is empty": {
cache: cache,
build: cacheBuild,
key: "key",
path: "",
expectedObjectName: "runner/longtoke/project/10/key",
},
"shared flag is set to true": {
cache: cache,
build: cacheBuild,
key: "key",
shared: true,
expectedObjectName: "project/10/key",
},
"shared flag is set to false": {
cache: cache,
build: cacheBuild,
key: "key",
shared: false,
expectedObjectName: "runner/longtoke/project/10/key",
},
"key escapes project namespace": {
cache: cache,
build: cacheBuild,
key: "../9/key",
expectedObjectName: "",
expectedError: "computed cache path outside of project bucket. Please remove `../` from cache key",
},
}
url := generateObjectName(cacheBuild, cache, "key") for name, test := range tests {
assert.Equal(t, "runner/longtoke/project/10/key", url) t.Run(name, func(t *testing.T) {
} cache.Path = test.path
cache.Shared = test.shared
func TestGetCacheObjectNameWhenSharedFlagIsFalseThenRunnerSegmentShouldNotBePresent(t *testing.T) { objectName, err := generateObjectName(test.build, test.cache, test.key)
cache := defaultCacheConfig()
cache.Shared = true
cacheBuild := defaultBuild(cache)
url := generateObjectName(cacheBuild, cache, "key") assert.Equal(t, test.expectedObjectName, objectName)
assert.Equal(t, "project/10/key", url) if len(test.expectedError) == 0 {
assert.NoError(t, err)
} else {
assert.EqualError(t, err, test.expectedError)
}
})
}
} }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment