Browse code

Verify layer tarstream

This adds verification for getting layer data out
of layerstore. These failures should only be possible
if layer metadata files have been manually changed
of if something is wrong with tar-split algorithm.

Failing early makes sure we don’t upload invalid data
to the registries where it would fail after someone
tries to pull it.

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
(cherry picked from commit e29e580f7fe628e936925681a4885d0b655bb151)

Tonis Tiigi authored on 2016/02/10 04:38:37
Showing 5 changed files
... ...
@@ -6,6 +6,7 @@ import (
6 6
 	"io/ioutil"
7 7
 	"os"
8 8
 	"path/filepath"
9
+	"strings"
9 10
 	"testing"
10 11
 
11 12
 	"github.com/docker/distribution/digest"
... ...
@@ -56,7 +57,7 @@ func newTestGraphDriver(t *testing.T) (graphdriver.Driver, func()) {
56 56
 	}
57 57
 }
58 58
 
59
-func newTestStore(t *testing.T) (Store, func()) {
59
+func newTestStore(t *testing.T) (Store, string, func()) {
60 60
 	td, err := ioutil.TempDir("", "layerstore-")
61 61
 	if err != nil {
62 62
 		t.Fatal(err)
... ...
@@ -72,7 +73,7 @@ func newTestStore(t *testing.T) (Store, func()) {
72 72
 		t.Fatal(err)
73 73
 	}
74 74
 
75
-	return ls, func() {
75
+	return ls, td, func() {
76 76
 		graphcleanup()
77 77
 		os.RemoveAll(td)
78 78
 	}
... ...
@@ -265,7 +266,7 @@ func assertLayerEqual(t *testing.T, l1, l2 Layer) {
265 265
 }
266 266
 
267 267
 func TestMountAndRegister(t *testing.T) {
268
-	ls, cleanup := newTestStore(t)
268
+	ls, _, cleanup := newTestStore(t)
269 269
 	defer cleanup()
270 270
 
271 271
 	li := initWithFiles(newTestFile("testfile.txt", []byte("some test data"), 0644))
... ...
@@ -306,7 +307,7 @@ func TestMountAndRegister(t *testing.T) {
306 306
 }
307 307
 
308 308
 func TestLayerRelease(t *testing.T) {
309
-	ls, cleanup := newTestStore(t)
309
+	ls, _, cleanup := newTestStore(t)
310 310
 	defer cleanup()
311 311
 
312 312
 	layer1, err := createLayer(ls, "", initWithFiles(newTestFile("layer1.txt", []byte("layer 1 file"), 0644)))
... ...
@@ -351,7 +352,7 @@ func TestLayerRelease(t *testing.T) {
351 351
 }
352 352
 
353 353
 func TestStoreRestore(t *testing.T) {
354
-	ls, cleanup := newTestStore(t)
354
+	ls, _, cleanup := newTestStore(t)
355 355
 	defer cleanup()
356 356
 
357 357
 	layer1, err := createLayer(ls, "", initWithFiles(newTestFile("layer1.txt", []byte("layer 1 file"), 0644)))
... ...
@@ -472,7 +473,7 @@ func TestStoreRestore(t *testing.T) {
472 472
 }
473 473
 
474 474
 func TestTarStreamStability(t *testing.T) {
475
-	ls, cleanup := newTestStore(t)
475
+	ls, _, cleanup := newTestStore(t)
476 476
 	defer cleanup()
477 477
 
478 478
 	files1 := []FileApplier{
... ...
@@ -668,7 +669,7 @@ func assertActivityCount(t *testing.T, l RWLayer, expected int) {
668 668
 }
669 669
 
670 670
 func TestRegisterExistingLayer(t *testing.T) {
671
-	ls, cleanup := newTestStore(t)
671
+	ls, _, cleanup := newTestStore(t)
672 672
 	defer cleanup()
673 673
 
674 674
 	baseFiles := []FileApplier{
... ...
@@ -702,3 +703,69 @@ func TestRegisterExistingLayer(t *testing.T) {
702 702
 
703 703
 	assertReferences(t, layer2a, layer2b)
704 704
 }
705
+
706
+func TestTarStreamVerification(t *testing.T) {
707
+	ls, tmpdir, cleanup := newTestStore(t)
708
+	defer cleanup()
709
+
710
+	files1 := []FileApplier{
711
+		newTestFile("/foo", []byte("abc"), 0644),
712
+		newTestFile("/bar", []byte("def"), 0644),
713
+	}
714
+	files2 := []FileApplier{
715
+		newTestFile("/foo", []byte("abc"), 0644),
716
+		newTestFile("/bar", []byte("def"), 0600), // different perm
717
+	}
718
+
719
+	tar1, err := tarFromFiles(files1...)
720
+	if err != nil {
721
+		t.Fatal(err)
722
+	}
723
+
724
+	tar2, err := tarFromFiles(files2...)
725
+	if err != nil {
726
+		t.Fatal(err)
727
+	}
728
+
729
+	layer1, err := ls.Register(bytes.NewReader(tar1), "")
730
+	if err != nil {
731
+		t.Fatal(err)
732
+	}
733
+
734
+	layer2, err := ls.Register(bytes.NewReader(tar2), "")
735
+	if err != nil {
736
+		t.Fatal(err)
737
+	}
738
+	id1 := digest.Digest(layer1.ChainID())
739
+	id2 := digest.Digest(layer2.ChainID())
740
+
741
+	// Replace tar data files
742
+	src, err := os.Open(filepath.Join(tmpdir, id1.Algorithm().String(), id1.Hex(), "tar-split.json.gz"))
743
+	if err != nil {
744
+		t.Fatal(err)
745
+	}
746
+
747
+	dst, err := os.Create(filepath.Join(tmpdir, id2.Algorithm().String(), id2.Hex(), "tar-split.json.gz"))
748
+	if err != nil {
749
+		t.Fatal(err)
750
+	}
751
+
752
+	if _, err := io.Copy(dst, src); err != nil {
753
+		t.Fatal(err)
754
+	}
755
+
756
+	src.Close()
757
+	dst.Close()
758
+
759
+	ts, err := layer2.TarStream()
760
+	if err != nil {
761
+		t.Fatal(err)
762
+	}
763
+	_, err = io.Copy(ioutil.Discard, ts)
764
+	if err == nil {
765
+		t.Fatal("expected data verification to fail")
766
+	}
767
+	if !strings.Contains(err.Error(), "could not verify layer data") {
768
+		t.Fatalf("wrong error returned from tarstream: %q", err)
769
+	}
770
+}
... ...
@@ -16,7 +16,7 @@ func graphDiffSize(ls Store, l Layer) (int64, error) {
16 16
 // Unix as Windows graph driver does not support Changes which is indirectly
17 17
 // invoked by calling DiffSize on the driver
18 18
 func TestLayerSize(t *testing.T) {
19
-	ls, cleanup := newTestStore(t)
19
+	ls, _, cleanup := newTestStore(t)
20 20
 	defer cleanup()
21 21
 
22 22
 	content1 := []byte("Base contents")
... ...
@@ -268,7 +268,7 @@ func TestLayerMigrationNoTarsplit(t *testing.T) {
268 268
 }
269 269
 
270 270
 func TestMountMigration(t *testing.T) {
271
-	ls, cleanup := newTestStore(t)
271
+	ls, _, cleanup := newTestStore(t)
272 272
 	defer cleanup()
273 273
 
274 274
 	baseFiles := []FileApplier{
... ...
@@ -11,7 +11,7 @@ import (
11 11
 )
12 12
 
13 13
 func TestMountInit(t *testing.T) {
14
-	ls, cleanup := newTestStore(t)
14
+	ls, _, cleanup := newTestStore(t)
15 15
 	defer cleanup()
16 16
 
17 17
 	basefile := newTestFile("testfile.txt", []byte("base data!"), 0644)
... ...
@@ -63,7 +63,7 @@ func TestMountInit(t *testing.T) {
63 63
 }
64 64
 
65 65
 func TestMountSize(t *testing.T) {
66
-	ls, cleanup := newTestStore(t)
66
+	ls, _, cleanup := newTestStore(t)
67 67
 	defer cleanup()
68 68
 
69 69
 	content1 := []byte("Base contents")
... ...
@@ -105,7 +105,7 @@ func TestMountSize(t *testing.T) {
105 105
 }
106 106
 
107 107
 func TestMountChanges(t *testing.T) {
108
-	ls, cleanup := newTestStore(t)
108
+	ls, _, cleanup := newTestStore(t)
109 109
 	defer cleanup()
110 110
 
111 111
 	basefiles := []FileApplier{
... ...
@@ -1,6 +1,11 @@
1 1
 package layer
2 2
 
3
-import "io"
3
+import (
4
+	"fmt"
5
+	"io"
6
+
7
+	"github.com/docker/distribution/digest"
8
+)
4 9
 
5 10
 type roLayer struct {
6 11
 	chainID    ChainID
... ...
@@ -29,7 +34,11 @@ func (rl *roLayer) TarStream() (io.ReadCloser, error) {
29 29
 			pw.Close()
30 30
 		}
31 31
 	}()
32
-	return pr, nil
32
+	rc, err := newVerifiedReadCloser(pr, digest.Digest(rl.diffID))
33
+	if err != nil {
34
+		return nil, err
35
+	}
36
+	return rc, nil
33 37
 }
34 38
 
35 39
 func (rl *roLayer) ChainID() ChainID {
... ...
@@ -117,3 +126,39 @@ func storeLayer(tx MetadataTransaction, layer *roLayer) error {
117 117
 
118 118
 	return nil
119 119
 }
120
+
121
+func newVerifiedReadCloser(rc io.ReadCloser, dgst digest.Digest) (io.ReadCloser, error) {
122
+	verifier, err := digest.NewDigestVerifier(dgst)
123
+	if err != nil {
124
+		return nil, err
125
+	}
126
+	return &verifiedReadCloser{
127
+		rc:       rc,
128
+		dgst:     dgst,
129
+		verifier: verifier,
130
+	}, nil
131
+}
132
+
133
+type verifiedReadCloser struct {
134
+	rc       io.ReadCloser
135
+	dgst     digest.Digest
136
+	verifier digest.Verifier
137
+}
138
+
139
+func (vrc *verifiedReadCloser) Read(p []byte) (n int, err error) {
140
+	n, err = vrc.rc.Read(p)
141
+	if n > 0 {
142
+		if n, err := vrc.verifier.Write(p[:n]); err != nil {
143
+			return n, err
144
+		}
145
+	}
146
+	if err == io.EOF {
147
+		if !vrc.verifier.Verified() {
148
+			err = fmt.Errorf("could not verify layer data for: %s. This may be because internal files in the layer store were modified. Re-pulling or rebuilding this image may resolve the issue", vrc.dgst)
149
+		}
150
+	}
151
+	return
152
+}
153
+func (vrc *verifiedReadCloser) Close() error {
154
+	return vrc.rc.Close()
155
+}