Skip to content

Commit c754536

Browse files
committed
repack: do not include Volume paths in new layers
This is (unfortunately) not mandated in the specification[1,2], but in order to avoid accidentally spilling private information into published layers (which is one use of Volumes) we must ignore all layers included in Config.Volumes. In future we should also add some flags or alernative ways of masking paths. [1]: opencontainers/image-spec#496 [2]: opencontainers/image-spec#504 Signed-off-by: Aleksa Sarai <[email protected]>
1 parent 2827f27 commit c754536

File tree

3 files changed

+224
-0
lines changed

3 files changed

+224
-0
lines changed

cmd/umoci/repack.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030
"github.com/openSUSE/umoci/oci/cas"
3131
igen "github.com/openSUSE/umoci/oci/config/generate"
3232
"github.com/openSUSE/umoci/oci/layer"
33+
"github.com/openSUSE/umoci/pkg/mtreefilter"
3334
ispec "github.com/opencontainers/image-spec/specs-go/v1"
3435
"github.com/pkg/errors"
3536
"github.com/urfave/cli"
@@ -155,6 +156,17 @@ func repack(ctx *cli.Context) error {
155156
"ndiff": len(diffs),
156157
}).Debugf("umoci: checked mtree spec")
157158

159+
// We need to mask config.Volumes.
160+
config, err := mutator.Config(context.Background())
161+
if err != nil {
162+
return errors.Wrap(err, "get config")
163+
}
164+
var volumes []string
165+
for v := range config.Volumes {
166+
volumes = append(volumes, v)
167+
}
168+
diffs = mtreefilter.FilterDeltas(diffs, mtreefilter.MaskFilter(volumes))
169+
158170
reader, err := layer.GenerateLayer(fullRootfsPath, diffs, &meta.MapOptions)
159171
if err != nil {
160172
return errors.Wrap(err, "generate diff layer")

pkg/mtreefilter/mask.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* umoci: Umoci Modifies Open Containers' Images
3+
* Copyright (C) 2017 SUSE LLC.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package mtreefilter
19+
20+
import (
21+
"path/filepath"
22+
23+
"github.com/apex/log"
24+
"github.com/vbatts/go-mtree"
25+
)
26+
27+
// FilterFunc is a function used when filtering deltas with FilterDeltas.
28+
type FilterFunc func(path string) bool
29+
30+
// isParent returns whether the path a is lexically an ancestor of the path b.
31+
func isParent(a, b string) bool {
32+
a = filepath.Clean(a)
33+
b = filepath.Clean(b)
34+
35+
for a != b {
36+
if b == filepath.Dir(b) {
37+
break
38+
}
39+
b = filepath.Dir(b)
40+
}
41+
return a == b
42+
}
43+
44+
// MaskFilter is a factory for FilterFuncs that will mask all InodeDelta paths
45+
// that are lexical children of any path in the mask slice. All paths are
46+
// considered to be relative to '/'.
47+
func MaskFilter(masks []string) FilterFunc {
48+
return func(path string) bool {
49+
// Convert path to relative-to-root.
50+
path = filepath.Clean(path)
51+
path = filepath.Join("/", path)
52+
53+
// Check that no masks are matched.
54+
for _, mask := range masks {
55+
// Mask also needs to be relative-to-root.
56+
mask = filepath.Clean(mask)
57+
mask = filepath.Join("/", mask)
58+
59+
// Is it a parent?
60+
if isParent(mask, path) {
61+
log.Debugf("maskfilter: ignoring path %q matched by mask %q", path, mask)
62+
return false
63+
}
64+
}
65+
66+
return true
67+
}
68+
}
69+
70+
// FilterDeltas is a helper function to easily filter []mtree.InodeDelta with a
71+
// filter function. Only entries which have `filter(delta.Path()) == true` will
72+
// be included in the returned slice.
73+
func FilterDeltas(deltas []mtree.InodeDelta, filter FilterFunc) []mtree.InodeDelta {
74+
var filtered []mtree.InodeDelta
75+
for _, delta := range deltas {
76+
if filter(delta.Path()) {
77+
filtered = append(filtered, delta)
78+
}
79+
}
80+
return filtered
81+
}

pkg/mtreefilter/mask_test.go

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
/*
2+
* umoci: Umoci Modifies Open Containers' Images
3+
* Copyright (C) 2017 SUSE LLC.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package mtreefilter
19+
20+
import (
21+
"io/ioutil"
22+
"os"
23+
"path/filepath"
24+
"testing"
25+
26+
"github.com/vbatts/go-mtree"
27+
)
28+
29+
func TestIsParent(t *testing.T) {
30+
for _, test := range []struct {
31+
parent, path string
32+
expected bool
33+
}{
34+
{"/", "/a", true},
35+
{"/", "/a/b/c", true},
36+
{"/", "/", true},
37+
{"/a path/", "/a path", true},
38+
{"/a nother path", "/a nother path/test", true},
39+
{"/a nother path", "/a nother path/test/1 2/ 33 /", true},
40+
{"/path1", "/path2", false},
41+
{"/pathA", "/PATHA", false},
42+
{"/pathC", "/path", false},
43+
{"/path9", "/", false},
44+
// Make sure it's not the same as filepath.HasPrefix.
45+
{"/a/b/c", "/a/b/c/d", true},
46+
{"/a/b/c", "/a/b/cd", false},
47+
{"/a/b/c", "/a/bcd", false},
48+
{"/a/bc", "/a/bcd", false},
49+
{"/abc", "/abcd", false},
50+
} {
51+
got := isParent(test.parent, test.path)
52+
if got != test.expected {
53+
t.Errorf("isParent(%q, %q) got %v expected %v", test.parent, test.path, got, test.expected)
54+
}
55+
}
56+
}
57+
58+
func TestMaskDeltas(t *testing.T) {
59+
dir, err := ioutil.TempDir("", "TestMaskDeltas-")
60+
if err != nil {
61+
t.Fatal(err)
62+
}
63+
defer os.RemoveAll(dir)
64+
65+
var mtreeKeywords []mtree.Keyword = append(mtree.DefaultKeywords, "sha256digest")
66+
67+
// Create some files.
68+
if err != ioutil.WriteFile(filepath.Join(dir, "file1"), []byte("contents"), 0644) {
69+
t.Fatal(err)
70+
}
71+
if err != ioutil.WriteFile(filepath.Join(dir, "file2"), []byte("another content"), 0644) {
72+
t.Fatal(err)
73+
}
74+
if err != os.MkdirAll(filepath.Join(dir, "dir", "child"), 0755) {
75+
t.Fatal(err)
76+
}
77+
if err != os.MkdirAll(filepath.Join(dir, "dir", "child2"), 0755) {
78+
t.Fatal(err)
79+
}
80+
if err != ioutil.WriteFile(filepath.Join(dir, "dir", "file 3"), []byte("more content"), 0644) {
81+
t.Fatal(err)
82+
}
83+
if err != ioutil.WriteFile(filepath.Join(dir, "dir", "child2", "4 files"), []byte("very content"), 0644) {
84+
t.Fatal(err)
85+
}
86+
87+
// Generate a diff.
88+
originalDh, err := mtree.Walk(dir, nil, mtreeKeywords, nil)
89+
if err != nil {
90+
t.Fatal(err)
91+
}
92+
93+
// Modify the root.
94+
if err := os.RemoveAll(filepath.Join(dir, "file2")); err != nil {
95+
t.Fatal(err)
96+
}
97+
if err := ioutil.WriteFile(filepath.Join(dir, "dir", "new"), []byte("more content"), 0755); err != nil {
98+
t.Fatal(err)
99+
}
100+
if err := ioutil.WriteFile(filepath.Join(dir, "file1"), []byte("different contents"), 0666); err != nil {
101+
t.Fatal(err)
102+
}
103+
104+
// Generate the set of diffs.
105+
newDh, err := mtree.Walk(dir, nil, mtreeKeywords, nil)
106+
if err != nil {
107+
t.Fatal(err)
108+
}
109+
diff, err := mtree.Compare(originalDh, newDh, mtreeKeywords)
110+
if err != nil {
111+
t.Fatal(err)
112+
}
113+
114+
for _, test := range []struct {
115+
paths []string
116+
len int
117+
}{
118+
{nil, len(diff)},
119+
{[]string{"/"}, 0},
120+
{[]string{"dir"}, 3},
121+
{[]string{filepath.Join("dir", "child2")}, 5},
122+
{[]string{"file2"}, 4},
123+
{[]string{"/", "file2"}, 0},
124+
{[]string{"file2", filepath.Join("dir", "child2")}, 4},
125+
} {
126+
newDiff := FilterDeltas(diff, MaskFilter(test.paths))
127+
if len(newDiff) != test.len {
128+
t.Errorf("diff %v expected %d got %d", test.paths, test.len, len(newDiff))
129+
}
130+
}
131+
}

0 commit comments

Comments
 (0)