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>
| ... | ... |
@@ -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 |