Browse code

Limit authz response buffer

When the authz response buffer limit is hit, perform a flush.
This prevents excessive buffer sizes, especially on large responses
(e.g. `/containers/<id>/archive` or `/containers/<id>/export`).

Signed-off-by: Brian Goff <cpuguy83@gmail.com>

Brian Goff authored on 2018/04/12 04:19:15
Showing 2 changed files
... ...
@@ -23,6 +23,7 @@ import (
23 23
 	"github.com/docker/docker/client"
24 24
 	"github.com/docker/docker/integration/internal/container"
25 25
 	"github.com/docker/docker/internal/test/environment"
26
+	"github.com/docker/docker/pkg/archive"
26 27
 	"github.com/docker/docker/pkg/authorization"
27 28
 	"github.com/gotestyourself/gotestyourself/assert"
28 29
 	"github.com/gotestyourself/gotestyourself/skip"
... ...
@@ -382,6 +383,56 @@ func TestAuthZPluginEnsureLoadImportWorking(t *testing.T) {
382 382
 	assert.NilError(t, err)
383 383
 }
384 384
 
385
+func TestAuthzPluginEnsureContainerCopyToFrom(t *testing.T) {
386
+	defer setupTestV1(t)()
387
+	ctrl.reqRes.Allow = true
388
+	ctrl.resRes.Allow = true
389
+	d.StartWithBusybox(t, "--authorization-plugin="+testAuthZPlugin, "--authorization-plugin="+testAuthZPlugin)
390
+
391
+	dir, err := ioutil.TempDir("", t.Name())
392
+	assert.Assert(t, err)
393
+	defer os.RemoveAll(dir)
394
+
395
+	f, err := ioutil.TempFile(dir, "send")
396
+	assert.Assert(t, err)
397
+	defer f.Close()
398
+
399
+	buf := make([]byte, 1024)
400
+	fileSize := len(buf) * 1024 * 10
401
+	for written := 0; written < fileSize; {
402
+		n, err := f.Write(buf)
403
+		assert.Assert(t, err)
404
+		written += n
405
+	}
406
+
407
+	ctx := context.Background()
408
+	client, err := d.NewClient()
409
+	assert.Assert(t, err)
410
+
411
+	cID := container.Run(t, ctx, client)
412
+	defer client.ContainerRemove(ctx, cID, types.ContainerRemoveOptions{Force: true})
413
+
414
+	_, err = f.Seek(0, io.SeekStart)
415
+	assert.Assert(t, err)
416
+
417
+	srcInfo, err := archive.CopyInfoSourcePath(f.Name(), false)
418
+	assert.Assert(t, err)
419
+	srcArchive, err := archive.TarResource(srcInfo)
420
+	assert.Assert(t, err)
421
+	defer srcArchive.Close()
422
+
423
+	dstDir, preparedArchive, err := archive.PrepareArchiveCopy(srcArchive, srcInfo, archive.CopyInfo{Path: "/test"})
424
+	assert.Assert(t, err)
425
+
426
+	err = client.CopyToContainer(ctx, cID, dstDir, preparedArchive, types.CopyToContainerOptions{})
427
+	assert.Assert(t, err)
428
+
429
+	rdr, _, err := client.CopyFromContainer(ctx, cID, "/test")
430
+	assert.Assert(t, err)
431
+	_, err = io.Copy(ioutil.Discard, rdr)
432
+	assert.Assert(t, err)
433
+}
434
+
385 435
 func imageSave(client client.APIClient, path, image string) error {
386 436
 	ctx := context.Background()
387 437
 	responseReader, err := client.ImageSave(ctx, []string{image})
... ...
@@ -47,6 +47,8 @@ func NewResponseModifier(rw http.ResponseWriter) ResponseModifier {
47 47
 	return &responseModifier{rw: rw, header: make(http.Header)}
48 48
 }
49 49
 
50
+const maxBufferSize = 64 * 1024
51
+
50 52
 // responseModifier is used as an adapter to http.ResponseWriter in order to manipulate and explore
51 53
 // the http request/response from docker daemon
52 54
 type responseModifier struct {
... ...
@@ -116,11 +118,13 @@ func (rm *responseModifier) OverrideHeader(b []byte) error {
116 116
 
117 117
 // Write stores the byte array inside content
118 118
 func (rm *responseModifier) Write(b []byte) (int, error) {
119
-
120 119
 	if rm.hijacked {
121 120
 		return rm.rw.Write(b)
122 121
 	}
123 122
 
123
+	if len(rm.body)+len(b) > maxBufferSize {
124
+		rm.Flush()
125
+	}
124 126
 	rm.body = append(rm.body, b...)
125 127
 	return len(b), nil
126 128
 }
... ...
@@ -192,11 +196,14 @@ func (rm *responseModifier) FlushAll() error {
192 192
 	var err error
193 193
 	if len(rm.body) > 0 {
194 194
 		// Write body
195
-		_, err = rm.rw.Write(rm.body)
195
+		var n int
196
+		n, err = rm.rw.Write(rm.body)
197
+		// TODO(@cpuguy83): there is now a relatively small buffer limit, instead of discarding our buffer here and
198
+		// allocating again later this should just keep using the same buffer and track the buffer position (like a bytes.Buffer with a fixed size)
199
+		rm.body = rm.body[n:]
196 200
 	}
197 201
 
198 202
 	// Clean previous data
199
-	rm.body = nil
200 203
 	rm.statusCode = 0
201 204
 	rm.header = http.Header{}
202 205
 	return err