mirror of
https://github.com/vmware-tanzu/velero.git
synced 2026-08-16 04:06:06 +00:00
archive.GetItemFilePath/GetVersionedItemFilePath joined the group resource, namespace and name into a path without checking the result against rootDir. Those components can come from backup contents - the additional items a RestoreItemAction returns are built from annotations on a backed up object - so a component containing ".." resolved to an arbitrary file on the Velero pod, which was then Stat'd, unmarshalled and restored as a Kubernetes object. Both helpers now return an error when the joined path escapes rootDir, and all callers handle it. rootDir is empty when building an entry path inside the backup tarball, so "." is used as the containment base for that relative form. Signed-off-by: chlins <chlins.zhang@gmail.com>
161 lines
5.0 KiB
Go
161 lines
5.0 KiB
Go
/*
|
|
Copyright the Velero contributors.
|
|
|
|
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 archive
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
|
"github.com/vmware-tanzu/velero/pkg/test"
|
|
)
|
|
|
|
func TestGetItemFilePath(t *testing.T) {
|
|
res, err := GetItemFilePath("root", "resource", "", "item")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "root/resources/resource/cluster/item.json", res)
|
|
|
|
res, err = GetItemFilePath("root", "resource", "namespace", "item")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "root/resources/resource/namespaces/namespace/item.json", res)
|
|
|
|
res, err = GetItemFilePath("", "resource", "", "item")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "resources/resource/cluster/item.json", res)
|
|
|
|
res, err = GetVersionedItemFilePath("root", "resource", "", "item", "")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "root/resources/resource/cluster/item.json", res)
|
|
|
|
res, err = GetVersionedItemFilePath("root", "resource", "namespace", "item", "")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "root/resources/resource/namespaces/namespace/item.json", res)
|
|
|
|
res, err = GetVersionedItemFilePath("root", "resource", "namespace", "item", "v1")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "root/resources/resource/v1/namespaces/namespace/item.json", res)
|
|
|
|
res, err = GetVersionedItemFilePath("root", "resource", "", "item", "v1")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "root/resources/resource/v1/cluster/item.json", res)
|
|
|
|
res, err = GetVersionedItemFilePath("", "resource", "", "item", "")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "resources/resource/cluster/item.json", res)
|
|
}
|
|
|
|
// TestGetItemFilePathRejectsPathTraversal verifies that a name or namespace containing
|
|
// ".." cannot address a file outside the extracted backup directory. These components can
|
|
// come from backup contents, for example the additional items a RestoreItemAction builds
|
|
// from annotations on a backed up object.
|
|
func TestGetItemFilePathRejectsPathTraversal(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
rootDir string
|
|
groupResource string
|
|
namespace string
|
|
itemName string
|
|
}{
|
|
{
|
|
name: "traversal in name escapes root",
|
|
rootDir: "/tmp/restore-dir",
|
|
groupResource: "secrets",
|
|
namespace: "x",
|
|
itemName: "../../../../../../root/.docker/config",
|
|
},
|
|
{
|
|
name: "traversal in namespace escapes root",
|
|
rootDir: "/tmp/restore-dir",
|
|
groupResource: "secrets",
|
|
namespace: "../../../../../../etc",
|
|
itemName: "passwd",
|
|
},
|
|
{
|
|
name: "traversal in group resource escapes root",
|
|
rootDir: "/tmp/restore-dir",
|
|
groupResource: "../../../../../../etc",
|
|
namespace: "",
|
|
itemName: "passwd",
|
|
},
|
|
{
|
|
name: "traversal escapes archive-relative root",
|
|
rootDir: "",
|
|
groupResource: "secrets",
|
|
namespace: "x",
|
|
itemName: "../../../../../../escape",
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
res, err := GetItemFilePath(tc.rootDir, tc.groupResource, tc.namespace, tc.itemName)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "escapes the backup directory")
|
|
assert.Empty(t, res)
|
|
|
|
res, err = GetVersionedItemFilePath(tc.rootDir, tc.groupResource, tc.namespace, tc.itemName, "v1")
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "escapes the backup directory")
|
|
assert.Empty(t, res)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestGetItemFilePathAllowsInnerDotDot verifies the containment check does not reject a
|
|
// path whose ".." segments resolve back inside the root directory.
|
|
func TestGetItemFilePathAllowsInnerDotDot(t *testing.T) {
|
|
res, err := GetItemFilePath("root", "resource", "namespaces/..", "item")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "root/resources/resource/namespaces/item.json", res)
|
|
}
|
|
|
|
func TestGetScopeDir(t *testing.T) {
|
|
res := GetScopeDir("")
|
|
assert.Equal(t, velerov1api.ClusterScopedDir, res)
|
|
|
|
res = GetScopeDir("test-namespace")
|
|
assert.Equal(t, velerov1api.NamespaceScopedDir, res)
|
|
}
|
|
|
|
func TestUnmarshal(t *testing.T) {
|
|
fs := test.NewFakeFileSystem()
|
|
filePath := "pod.json"
|
|
fileContent := `{
|
|
"apiVersion": "v1",
|
|
"kind": "Pod",
|
|
"metadata": {
|
|
"name": "example-pod"
|
|
},
|
|
"spec": {
|
|
"containers": [{
|
|
"name": "example-container",
|
|
"image": "example-image"
|
|
}]
|
|
}
|
|
}`
|
|
out, err := fs.Create(filePath)
|
|
require.NoError(t, err)
|
|
|
|
_, err = out.Write([]byte(fileContent))
|
|
require.NoError(t, err)
|
|
|
|
_, err = Unmarshal(fs, filePath)
|
|
require.NoError(t, err)
|
|
}
|