Remove local load restrictions.

This commit is contained in:
Jeffrey Regan
2019-04-18 19:25:06 -07:00
parent 1b1f91580e
commit 3c58c9d132
6 changed files with 290 additions and 86 deletions

View File

@@ -45,7 +45,8 @@ import (
// These paths refer to resources, patches,
// data for ConfigMaps and Secrets, etc.
//
// They must terminate in or below the root.
// The loadRestrictor may disallow certain paths
// or classes of paths.
//
// * bases (other kustomizations)
//
@@ -75,8 +76,8 @@ import (
// These restrictions assure that kustomizations
// are self-contained and relocatable, and impose
// some safety when relying on remote kustomizations,
// e.g. a ConfigMap generator specified to read
// from /etc/passwd will fail.
// e.g. a remotely loaded ConfigMap generator specified
// to read from /etc/passwd will fail.
//
type fileLoader struct {
// Loader that spawned this loader.
@@ -88,6 +89,9 @@ type fileLoader struct {
// paths relative to this directory.
root fs.ConfirmedDir
// Restricts behavior of Load function.
loadRestrictor LoadRestrictorFunc
// If this is non-nil, the files were
// obtained from the given repository.
repoSpec *git.RepoSpec
@@ -105,13 +109,17 @@ type fileLoader struct {
const CWD = "."
// NewFileLoaderAtCwd returns a loader that loads from ".".
// A convenience for kustomize edit commands.
func NewFileLoaderAtCwd(fSys fs.FileSystem) *fileLoader {
return newLoaderOrDie(fSys, CWD)
return newLoaderOrDie(
RestrictionRootOnly, fSys, CWD)
}
// NewFileLoaderAtRoot returns a loader that loads from "/".
// A convenience for tests.
func NewFileLoaderAtRoot(fSys fs.FileSystem) *fileLoader {
return newLoaderOrDie(fSys, string(filepath.Separator))
return newLoaderOrDie(
RestrictionRootOnly, fSys, string(filepath.Separator))
}
// Root returns the absolute path that is prepended to any
@@ -120,25 +128,28 @@ func (l *fileLoader) Root() string {
return l.root.String()
}
func newLoaderOrDie(fSys fs.FileSystem, path string) *fileLoader {
func newLoaderOrDie(
lr LoadRestrictorFunc, fSys fs.FileSystem, path string) *fileLoader {
root, err := demandDirectoryRoot(fSys, path)
if err != nil {
log.Fatalf("unable to make loader at '%s'; %v", path, err)
}
return newLoaderAtConfirmedDir(
root, fSys, nil, git.ClonerUsingGitExec)
lr, root, fSys, nil, git.ClonerUsingGitExec)
}
// newLoaderAtConfirmedDir returns a new fileLoader with given root.
func newLoaderAtConfirmedDir(
lr LoadRestrictorFunc,
root fs.ConfirmedDir, fSys fs.FileSystem,
referrer *fileLoader, cloner git.Cloner) *fileLoader {
return &fileLoader{
root: root,
referrer: referrer,
fSys: fSys,
cloner: cloner,
cleaner: func() error { return nil },
loadRestrictor: lr,
root: root,
referrer: referrer,
fSys: fSys,
cloner: cloner,
cleaner: func() error { return nil },
}
}
@@ -190,7 +201,7 @@ func (l *fileLoader) New(path string) (ifc.Loader, error) {
return nil, err
}
return newLoaderAtConfirmedDir(
root, l.fSys, l, l.cloner), nil
l.loadRestrictor, root, l.fSys, l, l.cloner), nil
}
// newLoaderAtGitClone returns a new Loader pinned to a temporary
@@ -216,12 +227,14 @@ func newLoaderAtGitClone(
repoSpec.AbsPath(), f)
}
return &fileLoader{
root: root,
referrer: referrer,
repoSpec: repoSpec,
fSys: fSys,
cloner: cloner,
cleaner: repoSpec.Cleaner(fSys),
// Clones never allowed to escape root.
loadRestrictor: RestrictionRootOnly,
root: root,
referrer: referrer,
repoSpec: repoSpec,
fSys: fSys,
cloner: cloner,
cleaner: repoSpec.Cleaner(fSys),
}, nil
}
@@ -288,29 +301,16 @@ func (l *fileLoader) errIfRepoCycle(newRepoSpec *git.RepoSpec) error {
// Load returns the content of file at the given path,
// else an error. Relative paths are taken relative
// relative to the root.
// to the root.
func (l *fileLoader) Load(path string) ([]byte, error) {
if filepath.IsAbs(path) {
return nil, l.loadOutOfBounds(path)
if !filepath.IsAbs(path) {
path = l.root.Join(path)
}
d, f, err := l.fSys.CleanedAbs(l.root.Join(path))
path, err := l.loadRestrictor(l.fSys, l.root, path)
if err != nil {
return nil, err
}
if f == "" {
return nil, fmt.Errorf(
"'%s' must be a file (got d='%s')", path, d)
}
if !d.HasPrefix(l.root) {
return nil, l.loadOutOfBounds(path)
}
return l.fSys.ReadFile(d.Join(f))
}
func (l *fileLoader) loadOutOfBounds(path string) error {
return fmt.Errorf(
"security; file '%s' is not in or below '%s'",
path, l.root)
return l.fSys.ReadFile(path)
}
// Cleanup runs the cleaner.

View File

@@ -25,11 +25,10 @@ import (
"strings"
"testing"
"sigs.k8s.io/kustomize/pkg/git"
"sigs.k8s.io/kustomize/pkg/pgmconfig"
"sigs.k8s.io/kustomize/pkg/fs"
"sigs.k8s.io/kustomize/pkg/git"
"sigs.k8s.io/kustomize/pkg/ifc"
"sigs.k8s.io/kustomize/pkg/pgmconfig"
)
type testData struct {
@@ -207,46 +206,47 @@ func TestLoaderMisc(t *testing.T) {
}
}
func TestRestrictedLoadingInRealLoader(t *testing.T) {
// Create a structure like this
//
// /tmp/kustomize-test-SZwCZYjySj
// ├── base
// │ ├── okayData
// │ ├── symLinkToOkayData -> okayData
// │ └── symLinkToForbiddenData -> ../forbiddenData
// ── forbiddenData
//
const (
contentOk = "hi there, i'm OK data"
contentExteriorData = "i am data from outside the root"
)
// Create a structure like this
//
// /tmp/kustomize-test-random
// ── base
// │ ├── okayData
// │ ├── symLinkToOkayData -> okayData
// │ └── symLinkToExteriorData -> ../exteriorData
// └── exteriorData
//
func commonSetupForLoaderRestrictionTest() (string, fs.FileSystem, error) {
dir, err := ioutil.TempDir("", "kustomize-test-")
if err != nil {
t.Fatalf("unexpected error: %v", err)
return "", nil, err
}
defer os.RemoveAll(dir)
fSys := fs.MakeRealFS()
fSys.Mkdir(filepath.Join(dir, "base"))
contentOk := "hi there, i'm OK data"
fSys.WriteFile(
filepath.Join(dir, "base", "okayData"), []byte(contentOk))
contentForbidden := "don't be looking at me"
fSys.WriteFile(
filepath.Join(dir, "forbiddenData"), []byte(contentForbidden))
filepath.Join(dir, "exteriorData"), []byte(contentExteriorData))
os.Symlink(
filepath.Join(dir, "base", "okayData"),
filepath.Join(dir, "base", "symLinkToOkayData"))
os.Symlink(
filepath.Join(dir, "forbiddenData"),
filepath.Join(dir, "base", "symLinkToForbiddenData"))
filepath.Join(dir, "exteriorData"),
filepath.Join(dir, "base", "symLinkToExteriorData"))
return dir, fSys, nil
}
var l ifc.Loader
l = newLoaderOrDie(fSys, dir)
// Sanity checks - loading from perspective of "dir".
// Everything works, including reading from a subdirectory.
// Make sure everything works when loading files
// in or below the loader root.
func doSanityChecksAndDropIntoBase(
t *testing.T, l ifc.Loader) ifc.Loader {
data, err := l.Load(path.Join("base", "okayData"))
if err != nil {
t.Fatalf("unexpected error: %v", err)
@@ -254,11 +254,11 @@ func TestRestrictedLoadingInRealLoader(t *testing.T) {
if string(data) != contentOk {
t.Fatalf("unexpected content: %v", data)
}
data, err = l.Load("forbiddenData")
data, err = l.Load("exteriorData")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if string(data) != contentForbidden {
if string(data) != contentExteriorData {
t.Fatalf("unexpected content: %v", data)
}
@@ -285,9 +285,24 @@ func TestRestrictedLoadingInRealLoader(t *testing.T) {
if string(data) != contentOk {
t.Fatalf("unexpected content: %v", data)
}
return l
}
// Reading symlink to forbiddenData fails.
_, err = l.Load("symLinkToForbiddenData")
func TestRestrictionRootInRealLoader(t *testing.T) {
dir, fSys, err := commonSetupForLoaderRestrictionTest()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
defer os.RemoveAll(dir)
var l ifc.Loader
l = newLoaderOrDie(RestrictionRootOnly, fSys, dir)
l = doSanityChecksAndDropIntoBase(t, l)
// Reading symlink to exteriorData fails.
_, err = l.Load("symLinkToExteriorData")
if err == nil {
t.Fatalf("expected error")
}
@@ -297,7 +312,7 @@ func TestRestrictedLoadingInRealLoader(t *testing.T) {
// Attempt to read "up" fails, though earlier we were
// able to read this file when root was "..".
_, err = l.Load("../forbiddenData")
_, err = l.Load("../exteriorData")
if err == nil {
t.Fatalf("expected error")
}
@@ -306,6 +321,32 @@ func TestRestrictedLoadingInRealLoader(t *testing.T) {
}
}
func TestRestrictionNoneInRealLoader(t *testing.T) {
dir, fSys, err := commonSetupForLoaderRestrictionTest()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
defer os.RemoveAll(dir)
var l ifc.Loader
l = newLoaderOrDie(RestrictionNone, fSys, dir)
l = doSanityChecksAndDropIntoBase(t, l)
// Reading symlink to exteriorData works.
_, err = l.Load("symLinkToExteriorData")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
// Attempt to read "up" works.
_, err = l.Load("../exteriorData")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
}
func splitOnNthSlash(v string, n int) (string, string) {
left := ""
for i := 0; i < n; i++ {
@@ -393,7 +434,7 @@ func TestLoaderDisallowsLocalBaseFromRemoteOverlay(t *testing.T) {
// Establish that a local overlay can navigate
// to the local bases.
l1 = newLoaderOrDie(fSys, cloneRoot+"/foo/overlay")
l1 = newLoaderOrDie(RestrictionRootOnly, fSys, cloneRoot+"/foo/overlay")
if l1.Root() != cloneRoot+"/foo/overlay" {
t.Fatalf("unexpected root %s", l1.Root())
}
@@ -468,7 +509,7 @@ func TestLocalLoaderReferencingGitBase(t *testing.T) {
t.Fatalf("unexpected err: %v\n", err)
}
l1 := newLoaderAtConfirmedDir(
root, fSys, nil,
RestrictionRootOnly, root, fSys, nil,
git.DoNothingCloner(fs.ConfirmedDir(cloneRoot)))
if l1.Root() != topDir {
t.Fatalf("unexpected root %s", l1.Root())

View File

@@ -23,17 +23,24 @@ import (
"sigs.k8s.io/kustomize/pkg/ifc"
)
// NewLoader returns a Loader.
func NewLoader(path string, fSys fs.FileSystem) (ifc.Loader, error) {
repoSpec, err := git.NewRepoSpecFromUrl(path)
// NewLoader returns a Loader pointed at the given target.
// If the target is remote, the loader will be restricted
// to the root and below only. If the target is local, the
// loader will have no restrictions. If the local target
// attempts to transitively load remote bases, they will all
// be root-only restricted.
func NewLoader(
target string, fSys fs.FileSystem) (ifc.Loader, error) {
repoSpec, err := git.NewRepoSpecFromUrl(target)
if err == nil {
// The target qualifies as a remote git target.
return newLoaderAtGitClone(
repoSpec, fSys, nil, git.ClonerUsingGitExec)
}
root, err := demandDirectoryRoot(fSys, path)
root, err := demandDirectoryRoot(fSys, target)
if err != nil {
return nil, err
}
return newLoaderAtConfirmedDir(
root, fSys, nil, git.ClonerUsingGitExec), nil
RestrictionNone, root, fSys, nil, git.ClonerUsingGitExec), nil
}

View File

@@ -0,0 +1,48 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package loader
import (
"fmt"
"sigs.k8s.io/kustomize/pkg/fs"
)
type LoadRestrictorFunc func(
fs.FileSystem, fs.ConfirmedDir, string) (string, error)
func RestrictionRootOnly(
fSys fs.FileSystem, root fs.ConfirmedDir, path string) (string, error) {
d, f, err := fSys.CleanedAbs(path)
if err != nil {
return "", err
}
if f == "" {
return "", fmt.Errorf("'%s' must be a file", path)
}
if !d.HasPrefix(root) {
return "", fmt.Errorf(
"security; file '%s' is not in or below '%s'",
path, root)
}
return d.Join(f), nil
}
func RestrictionNone(
_ fs.FileSystem, _ fs.ConfirmedDir, path string) (string, error) {
return path, nil
}

View File

@@ -0,0 +1,74 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package loader
import (
"strings"
"testing"
"sigs.k8s.io/kustomize/pkg/fs"
)
func TestRestrictionNone(t *testing.T) {
fSys := fs.MakeFakeFS()
root := fs.ConfirmedDir("irrelevant")
path := "whatever"
p, err := RestrictionNone(fSys, root, path)
if err != nil {
t.Fatal(err)
}
if p != path {
t.Fatalf("expected '%s', got '%s'", path, p)
}
}
func TestRestrictionRootOnly(t *testing.T) {
fSys := fs.MakeFakeFS()
root := fs.ConfirmedDir("/tmp/foo")
path := "/tmp/foo/whatever/beans"
p, err := RestrictionRootOnly(fSys, root, path)
if err != nil {
t.Fatal(err)
}
if p != path {
t.Fatalf("expected '%s', got '%s'", path, p)
}
// Legal.
path = "/tmp/foo/whatever/../../foo/whatever"
p, err = RestrictionRootOnly(fSys, root, path)
if err != nil {
t.Fatal(err)
}
path = "/tmp/foo/whatever"
if p != path {
t.Fatalf("expected '%s', got '%s'", path, p)
}
// Illegal.
path = "/tmp/illegal"
_, err = RestrictionRootOnly(fSys, root, path)
if err == nil {
t.Fatal("should have an error")
}
if !strings.Contains(
err.Error(),
"file '/tmp/illegal' is not in or below '/tmp/foo'") {
t.Fatalf("unexpected err: %s", err)
}
}

View File

@@ -17,7 +17,6 @@ limitations under the License.
package target_test
import (
"strings"
"testing"
)
@@ -182,7 +181,7 @@ spec:
`)
}
func TestSharedPatchDisAllowed(t *testing.T) {
func TestSharedPatchAllowed(t *testing.T) {
th := NewKustTestHarness(t, "/app/overlay")
writeSmallBase(th)
th.writeK("/app/overlay", `
@@ -201,15 +200,50 @@ metadata:
spec:
replicas: 1000
`)
_, err := th.makeKustTarget().MakeCustomizedResMap()
if err == nil {
t.Fatalf("expected error")
}
if !strings.Contains(
err.Error(),
"security; file '../shared/deployment-patch.yaml' is not in or below '/app/overlay'") {
t.Fatalf("unexpected error: %s", err)
m, err := th.makeKustTarget().MakeCustomizedResMap()
if err != nil {
t.Fatalf("Err: %v", err)
}
th.assertActualEqualsExpected(m, `
apiVersion: v1
kind: Service
metadata:
labels:
app: myApp
env: prod
name: a-myService
spec:
ports:
- port: 7002
selector:
app: myApp
backend: bungie
env: prod
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: myApp
env: prod
name: a-myDeployment
spec:
replicas: 1000
selector:
matchLabels:
app: myApp
env: prod
template:
metadata:
labels:
app: myApp
backend: awesome
env: prod
spec:
containers:
- image: whatever
name: whatever
`)
}
func TestSmallOverlayJSONPatch(t *testing.T) {