Skip to content

Commit 9d5491c

Browse files
authored
fix containerized function mounts issue (kubernetes-sigs#4489)
* fix containerized function mounts issue * skip path test on windows * move test out of temp dir * update tests to deal with new working dir restrictions * code review
1 parent cf89eae commit 9d5491c

8 files changed

Lines changed: 252 additions & 56 deletions

File tree

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ kustomize: $(MYGOBIN)/kustomize
8282
# Used to add non-default compilation flags when experimenting with
8383
# plugin-to-api compatibility checks.
8484
.PHONY: build-kustomize-api
85-
build-kustomize-api: $(builtinplugins)
85+
build-kustomize-api: $(MYGOBIN)/goimports $(builtinplugins)
8686
cd api; $(MAKE) build
8787

8888
.PHONY: generate-kustomize-api

api/internal/plugins/loader/loader.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,17 @@ func (l *Loader) makeBuiltinPlugin(r resid.Gvk) (resmap.Configurable, error) {
228228
func (l *Loader) loadPlugin(res *resource.Resource) (resmap.Configurable, error) {
229229
spec := fnplugin.GetFunctionSpec(res)
230230
if spec != nil {
231+
// validation check that function mounts are under the current kustomization directory
232+
for _, mount := range spec.Container.StorageMounts {
233+
if filepath.IsAbs(mount.Src) {
234+
return nil, errors.New(fmt.Sprintf("plugin %s with mount path '%s' is not permitted; "+
235+
"mount paths must be relative to the current kustomization directory", res.OrgId(), mount.Src))
236+
}
237+
if strings.HasPrefix(filepath.Clean(mount.Src), "../") {
238+
return nil, errors.New(fmt.Sprintf("plugin %s with mount path '%s' is not permitted; "+
239+
"mount paths must be under the current kustomization directory", res.OrgId(), mount.Src))
240+
}
241+
}
231242
return fnplugin.NewFnPlugin(&l.pc.FnpLoadingOptions), nil
232243
}
233244
return l.loadExecOrGoPlugin(res.OrgId())

api/krusty/fnplugin_test.go

Lines changed: 199 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"testing"
1111

1212
"github.com/stretchr/testify/assert"
13+
. "sigs.k8s.io/kustomize/api/krusty"
1314
kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest"
1415
"sigs.k8s.io/kustomize/kyaml/filesys"
1516
)
@@ -535,30 +536,33 @@ spec:
535536

536537
func TestFnContainerTransformerWithConfig(t *testing.T) {
537538
skipIfNoDocker(t)
538-
539-
// Function plugins should not need the env setup done by MakeEnhancedHarness
540539
th := kusttest_test.MakeHarness(t)
541-
542-
th.WriteK(".", `
540+
o := th.MakeOptionsPluginsEnabled()
541+
fSys := filesys.MakeFsOnDisk()
542+
b := MakeKustomizer(&o)
543+
tmpDir, err := filesys.NewTmpConfirmedDir()
544+
assert.NoError(t, err)
545+
assert.NoError(t, fSys.WriteFile(filepath.Join(tmpDir.String(), "kustomization.yaml"), []byte(`
543546
resources:
544547
- data1.yaml
545548
- data2.yaml
546549
transformers:
547550
- label_namespace.yaml
548-
`)
549-
550-
th.WriteF("data1.yaml", `apiVersion: v1
551+
`)))
552+
assert.NoError(t, fSys.WriteFile(filepath.Join(tmpDir.String(), "data1.yaml"), []byte(`
553+
apiVersion: v1
551554
kind: Namespace
552555
metadata:
553556
name: my-namespace
554-
`)
555-
th.WriteF("data2.yaml", `apiVersion: v1
557+
`)))
558+
assert.NoError(t, fSys.WriteFile(filepath.Join(tmpDir.String(), "data2.yaml"), []byte(`
559+
apiVersion: v1
556560
kind: Namespace
557561
metadata:
558562
name: another-namespace
559-
`)
560-
561-
th.WriteF("label_namespace.yaml", `apiVersion: v1
563+
`)))
564+
assert.NoError(t, fSys.WriteFile(filepath.Join(tmpDir.String(), "label_namespace.yaml"), []byte(`
565+
apiVersion: v1
562566
kind: ConfigMap
563567
metadata:
564568
name: label_namespace
@@ -569,11 +573,14 @@ metadata:
569573
data:
570574
label_name: my-ns-name
571575
label_value: function-test
572-
`)
573-
574-
m := th.Run(".", th.MakeOptionsPluginsEnabled())
575-
th.AssertActualEqualsExpected(m, `
576-
apiVersion: v1
576+
`)))
577+
m, err := b.Run(
578+
fSys,
579+
tmpDir.String())
580+
assert.NoError(t, err)
581+
actual, err := m.AsYaml()
582+
assert.NoError(t, err)
583+
assert.Equal(t, `apiVersion: v1
577584
kind: Namespace
578585
metadata:
579586
labels:
@@ -586,24 +593,22 @@ metadata:
586593
labels:
587594
my-ns-name: function-test
588595
name: another-namespace
589-
`)
596+
`, string(actual))
590597
}
591598

592599
func TestFnContainerEnvVars(t *testing.T) {
593600
skipIfNoDocker(t)
594-
595-
// Function plugins should not need the env setup done by MakeEnhancedHarness
596601
th := kusttest_test.MakeHarness(t)
597-
598-
th.WriteK(".", `
602+
o := th.MakeOptionsPluginsEnabled()
603+
fSys := filesys.MakeFsOnDisk()
604+
b := MakeKustomizer(&o)
605+
tmpDir, err := filesys.NewTmpConfirmedDir()
606+
assert.NoError(t, err)
607+
assert.NoError(t, fSys.WriteFile(filepath.Join(tmpDir.String(), "kustomization.yaml"), []byte(`
599608
generators:
600609
- gener.yaml
601-
`)
602-
603-
// TODO: cheange image to gcr.io/kpt-functions/templater:stable
604-
// when https://github.com/GoogleContainerTools/kpt-functions-catalog/pull/103
605-
// is merged
606-
th.WriteF("gener.yaml", `
610+
`)))
611+
assert.NoError(t, fSys.WriteFile(filepath.Join(tmpDir.String(), "gener.yaml"), []byte(`
607612
apiVersion: v1
608613
kind: ConfigMap
609614
metadata:
@@ -622,14 +627,176 @@ data:
622627
name: env
623628
data:
624629
value: '{{ env "TESTTEMPLATE" }}'
625-
`)
626-
m := th.Run(".", th.MakeOptionsPluginsEnabled())
627-
th.AssertActualEqualsExpected(m, `
628-
apiVersion: v1
630+
`)))
631+
m, err := b.Run(
632+
fSys,
633+
tmpDir.String())
634+
assert.NoError(t, err)
635+
actual, err := m.AsYaml()
636+
assert.NoError(t, err)
637+
assert.Equal(t, `apiVersion: v1
629638
data:
630639
value: value
631640
kind: ConfigMap
632641
metadata:
633642
name: env
643+
`, string(actual))
644+
}
645+
646+
func TestFnContainerFnMounts(t *testing.T) {
647+
skipIfNoDocker(t)
648+
th := kusttest_test.MakeHarness(t)
649+
o := th.MakeOptionsPluginsEnabled()
650+
fSys := filesys.MakeFsOnDisk()
651+
b := MakeKustomizer(&o)
652+
tmpDir, err := filesys.NewTmpConfirmedDir()
653+
assert.NoError(t, err)
654+
assert.NoError(t, fSys.WriteFile(filepath.Join(tmpDir.String(), "kustomization.yaml"), []byte(`
655+
generators:
656+
- gener.yaml
657+
`)))
658+
assert.NoError(t, fSys.WriteFile(filepath.Join(tmpDir.String(), "gener.yaml"), []byte(`
659+
apiVersion: v1alpha1
660+
kind: RenderHelmChart
661+
metadata:
662+
name: demo
663+
annotations:
664+
config.kubernetes.io/function: |
665+
container:
666+
image: gcr.io/kpt-fn/render-helm-chart:v0.1.0
667+
mounts:
668+
- type: "bind"
669+
src: "./charts"
670+
dst: "/tmp/charts"
671+
helmCharts:
672+
- name: helloworld-chart
673+
releaseName: test
674+
valuesFile: /tmp/charts/helloworld-values/values.yaml
675+
`)))
676+
assert.NoError(t, fSys.MkdirAll(filepath.Join(tmpDir.String(), "charts", "helloworld-chart", "templates")))
677+
assert.NoError(t, fSys.MkdirAll(filepath.Join(tmpDir.String(), "charts", "helloworld-values")))
678+
assert.NoError(t, fSys.WriteFile(filepath.Join(tmpDir.String(), "charts", "helloworld-chart", "Chart.yaml"), []byte(`
679+
apiVersion: v2
680+
name: helloworld-chart
681+
description: A Helm chart for Kubernetes
682+
type: application
683+
version: 0.1.0
684+
appVersion: 1.16.0
685+
`)))
686+
assert.NoError(t, fSys.WriteFile(filepath.Join(tmpDir.String(), "charts", "helloworld-chart", "templates", "deployment.yaml"), []byte(`
687+
apiVersion: apps/v1
688+
kind: Deployment
689+
metadata:
690+
name: name
691+
spec:
692+
replicas: {{ .Values.replicaCount }}
693+
`)))
694+
assert.NoError(t, fSys.WriteFile(filepath.Join(tmpDir.String(), "charts", "helloworld-values", "values.yaml"), []byte(`
695+
replicaCount: 5
696+
`)))
697+
m, err := b.Run(
698+
fSys,
699+
tmpDir.String())
700+
assert.NoError(t, err)
701+
actual, err := m.AsYaml()
702+
assert.NoError(t, err)
703+
assert.Equal(t, `apiVersion: apps/v1
704+
kind: Deployment
705+
metadata:
706+
name: name
707+
spec:
708+
replicas: 5
709+
`, string(actual))
710+
}
711+
712+
func TestFnContainerMountsLoadRestrictions_absolute(t *testing.T) {
713+
skipIfNoDocker(t)
714+
th := kusttest_test.MakeHarness(t)
715+
o := th.MakeOptionsPluginsEnabled()
716+
fSys := filesys.MakeFsOnDisk()
717+
b := MakeKustomizer(&o)
718+
tmpDir, err := filesys.NewTmpConfirmedDir()
719+
assert.NoError(t, err)
720+
assert.NoError(t, fSys.WriteFile(filepath.Join(tmpDir.String(), "kustomization.yaml"), []byte(`
721+
generators:
722+
- |-
723+
apiVersion: v1alpha1
724+
kind: RenderHelmChart
725+
metadata:
726+
name: demo
727+
annotations:
728+
config.kubernetes.io/function: |
729+
container:
730+
image: gcr.io/kpt-fn/render-helm-chart:v0.1.0
731+
mounts:
732+
- type: "bind"
733+
src: "/tmp/dir"
734+
dst: "/tmp/charts"
735+
`)))
736+
_, err = b.Run(
737+
fSys,
738+
tmpDir.String())
739+
assert.Error(t, err)
740+
assert.Contains(t, err.Error(), "loading generator plugins: plugin RenderHelmChart."+
741+
"v1alpha1.[noGrp]/demo.[noNs] with mount path '/tmp/dir' is not permitted; mount paths must"+
742+
" be relative to the current kustomization directory")
743+
}
744+
745+
func TestFnContainerMountsLoadRestrictions_outsideCurrentDir(t *testing.T) {
746+
skipIfNoDocker(t)
747+
th := kusttest_test.MakeHarness(t)
748+
o := th.MakeOptionsPluginsEnabled()
749+
fSys := filesys.MakeFsOnDisk()
750+
b := MakeKustomizer(&o)
751+
tmpDir, err := filesys.NewTmpConfirmedDir()
752+
assert.NoError(t, err)
753+
assert.NoError(t, fSys.WriteFile(filepath.Join(tmpDir.String(), "kustomization.yaml"), []byte(`
754+
generators:
755+
- |-
756+
apiVersion: v1alpha1
757+
kind: RenderHelmChart
758+
metadata:
759+
name: demo
760+
annotations:
761+
config.kubernetes.io/function: |
762+
container:
763+
image: gcr.io/kpt-fn/render-helm-chart:v0.1.0
764+
mounts:
765+
- type: "bind"
766+
src: "./tmp/../../dir"
767+
dst: "/tmp/charts"
768+
`)))
769+
_, err = b.Run(
770+
fSys,
771+
tmpDir.String())
772+
assert.Error(t, err)
773+
assert.Contains(t, err.Error(), "loading generator plugins: plugin RenderHelmChart."+
774+
"v1alpha1.[noGrp]/demo.[noNs] with mount path './tmp/../../dir' is not permitted; mount paths must "+
775+
"be under the current kustomization directory")
776+
}
777+
778+
func TestFnContainerMountsLoadRestrictions_root(t *testing.T) {
779+
skipIfNoDocker(t)
780+
th := kusttest_test.MakeHarness(t)
781+
782+
th.WriteK(".", `
783+
generators:
784+
- gener.yaml
785+
`)
786+
// Create generator config
787+
th.WriteF("gener.yaml", `
788+
apiVersion: examples.config.kubernetes.io/v1beta1
789+
kind: CockroachDB
790+
metadata:
791+
name: demo
792+
annotations:
793+
config.kubernetes.io/function: |
794+
container:
795+
image: gcr.io/kustomize-functions/example-cockroachdb:v0.1.0
796+
spec:
797+
replicas: 3
634798
`)
799+
err := th.RunWithErr(".", th.MakeOptionsPluginsEnabled())
800+
assert.Error(t, err)
801+
assert.EqualError(t, err, "couldn't execute function: root working directory '/' not allowed")
635802
}

kyaml/fn/runtime/container/container.go

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@ package container
66
import (
77
"fmt"
88
"os"
9+
"path/filepath"
910

11+
"sigs.k8s.io/kustomize/kyaml/errors"
1012
runtimeexec "sigs.k8s.io/kustomize/kyaml/fn/runtime/exec"
1113
"sigs.k8s.io/kustomize/kyaml/fn/runtime/runtimeutil"
12-
1314
"sigs.k8s.io/kustomize/kyaml/yaml"
1415
)
1516

@@ -151,11 +152,14 @@ func (c *Filter) setupExec() error {
151152
if c.Exec.Path != "" {
152153
return nil
153154
}
154-
wd, err := os.Getwd()
155-
if err != nil {
156-
return err
155+
156+
if c.Exec.WorkingDir == "" {
157+
wd, err := os.Getwd()
158+
if err != nil {
159+
return errors.Wrap(err)
160+
}
161+
c.Exec.WorkingDir = wd
157162
}
158-
c.Exec.WorkingDir = wd
159163

160164
path, args := c.getCommand()
161165
c.Exec.Path = path
@@ -183,8 +187,11 @@ func (c *Filter) getCommand() (string, []string) {
183187
// note: don't make fs readonly because things like heredoc rely on writing tmp files
184188
}
185189

186-
// TODO(joncwong): Allow StorageMount fields to have default values.
187190
for _, storageMount := range c.StorageMounts {
191+
// convert declarative relative paths to absolute (otherwise docker will throw an error)
192+
if !filepath.IsAbs(storageMount.Src) {
193+
storageMount.Src = filepath.Join(c.Exec.WorkingDir, storageMount.Src)
194+
}
188195
args = append(args, "--mount", storageMount.String())
189196
}
190197

0 commit comments

Comments
 (0)