Skip to content

secret create, config create: refactor, use limit reader, and touch up errors#5912

Merged
thaJeztah merged 2 commits intodocker:masterfrom
thaJeztah:refactor_secret_config_create
Mar 18, 2025
Merged

secret create, config create: refactor, use limit reader, and touch up errors#5912
thaJeztah merged 2 commits intodocker:masterfrom
thaJeztah:refactor_secret_config_create

Conversation

@thaJeztah
Copy link
Member

secret create: refactor, use limit reader, and touch up errors

Swarm has size constraints on the size of secrets, but the client-side would
read content into memory, regardless its size. This could lead to either the
client reading too much into memory, or it sending data that's larger than
the size limit of gRPC, which resulted in the error not being handled by
SwarmKit and a generic gRPC error returned.

Reading a secret from a file was added in moby@c6f0b7f, which used a
system.OpenSequential for reading (FILE_FLAG_SEQUENTIAL_SCAN). While
there could be a very marginal benefit to prevent polluting the system's
cache (Windows won’t aggressively keep it in the cache, freeing up system
memory for other tasks). These details were not documented in code, and
possibly may be too marginal, but adding a comment to outline won't hurt
so this patch also adds a comment.

This patch:

  • Rewrites readSecretData to not return a nil-error if no file was
    set, in stead only calling it when not using a driver.
  • Implements reading the data with a limit-reader to prevent reading
    large files into memory.
  • The limit is based on SwarmKits limits (MaxSecretSize), but made
    twice that size, just in case larger sizes are supported in future;
    the main goal is to have some constraints, and to prevent hitting
    the gRPC limit.
  • Updates some error messages to include STDIN (when used), or the
    filename (when used).

Before this patch:

ls -lh largefile
-rw-------  1 thajeztah  staff   8.1M Mar  9 00:19 largefile

docker secret create nosuchfile ./nosuchfile
Error reading content from "./nosuchfile": open ./nosuchfile: no such file or directory

docker secret create toolarge ./largefile
Error response from daemon: rpc error: code = ResourceExhausted desc = grpc: received message larger than max (8462870 vs. 4194304)

docker secret create empty ./emptyfile
Error response from daemon: rpc error: code = InvalidArgument desc = secret data must be larger than 0 and less than 512000 bytes

cat ./largefile | docker secret create toolarge -
Error response from daemon: rpc error: code = ResourceExhausted desc = grpc: received message larger than max (8462870 vs. 4194304)

cat ./emptyfile | docker secret create empty -
Error response from daemon: rpc error: code = InvalidArgument desc = secret data must be larger than 0 and less than 512000 bytes

With this patch:

docker secret create nosuchfile ./nosuchfile
error reading from ./nosuchfile: open ./nosuchfile: no such file or directory

docker secret create empty ./emptyfile
error reading from ./emptyfile: data is empty

docker secret create toolarge ./largefile
Error response from daemon: rpc error: code = InvalidArgument desc = secret data must be larger than 0 and less than 512000 bytes

cat ./largefile | docker secret create toolarge -
Error response from daemon: rpc error: code = InvalidArgument desc = secret data must be larger than 0 and less than 512000 bytes

cat ./emptyfile | docker secret create empty -
error reading from STDIN: data is empty

config create: refactor, use limit reader, and touch up errors

Swarm has size constraints on the size of configs, but the client-side would
read content into memory, regardless its size. This could lead to either the
client reading too much into memory, or it sending data that's larger than
the size limit of gRPC, which resulted in the error not being handled by
SwarmKit and a generic gRPC error returned.

Reading a config from a file used a system.OpenSequential for reading
(FILE_FLAG_SEQUENTIAL_SCAN). While there could be a very marginal benefit
to prevent polluting the system's cache (Windows won’t aggressively keep it
in the cache, freeing up system memory for other tasks). These details were
not documented in code, and possibly may be too marginal, but adding a comment
to outline won't hurt so this patch also adds a comment.

This patch:

  • Factors out the reading code to a readConfigData, analogous to the
    equivalent in secret create.
  • Implements reading the data with a limit-reader to prevent reading
    large files into memory.
  • The limit is based on SwarmKits limits (MaxConfigSize), but made
    twice that size, just in case larger sizes are supported in future;
    the main goal is to have some constraints, and to prevent hitting
    the gRPC limit.
  • Updates some error messages to include STDIN (when used), or the
    filename (when used).

Before this patch:

ls -lh largefile
-rw-------  1 thajeztah  staff   8.1M Mar  9 00:19 largefile

docker config create nosuchfile ./nosuchfile
Error reading content from "./nosuchfile": open ./nosuchfile: no such file or directory

docker config create toolarge ./largefile
Error response from daemon: rpc error: code = ResourceExhausted desc = grpc: received message larger than max (8462870 vs. 4194304)

docker config create empty ./emptyfile
Error response from daemon: rpc error: code = InvalidArgument desc = config data must be larger than 0 and less than 1024000 bytes

cat ./largefile | docker config create toolarge -
Error response from daemon: rpc error: code = ResourceExhausted desc = grpc: received message larger than max (8462870 vs. 4194304)

cat ./emptyfile | docker config create empty -
Error response from daemon: rpc error: code = InvalidArgument desc = config data must be larger than 0 and less than 1024000 bytes

With this patch:

docker config create nosuchfile ./nosuchfile
error reading from ./nosuchfile: open ./nosuchfile: no such file or directory

docker config create empty ./emptyfile
error reading from ./emptyfile: data is empty

docker config create toolarge ./largefile
Error response from daemon: rpc error: code = InvalidArgument desc = config data must be larger than 0 and less than 1024000 bytes

cat ./largefile | docker config create toolarge -
Error response from daemon: rpc error: code = InvalidArgument desc = secret data must be larger than 0 and less than 1024000 bytes

cat ./emptyfile | docker config create empty -
error reading from STDIN: data is empty

- Human readable description for the release notes

- A picture of a cute animal (not mandatory but encouraged)

@thaJeztah thaJeztah added status/2-code-review area/swarm kind/refactor PR's that refactor, or clean-up code labels Mar 9, 2025
@thaJeztah thaJeztah changed the title config create: refactor, use limit reader, and touch up errors secret create, config create: refactor, use limit reader, and touch up errors Mar 9, 2025
@codecov-commenter
Copy link

codecov-commenter commented Mar 9, 2025

Codecov Report

Attention: Patch coverage is 48.75000% with 41 lines in your changes missing coverage. Please review.

Project coverage is 59.29%. Comparing base (2d74733) to head (d6d8ca6).
Report is 3 commits behind head on master.

❌ Your patch status has failed because the patch coverage (48.75%) is below the target coverage (50.00%). You can increase the patch coverage or adjust the target coverage.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #5912      +/-   ##
==========================================
- Coverage   59.33%   59.29%   -0.04%     
==========================================
  Files         358      358              
  Lines       29783    29830      +47     
==========================================
+ Hits        17672    17688      +16     
- Misses      11142    11170      +28     
- Partials      969      972       +3     
🚀 New features to boost your workflow:
  • Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment on lines +93 to +99
defer func() {
if retErr != nil {
retErr = fmt.Errorf("error reading from %s: %w", fileName, retErr)
} else if len(data) == 0 {
retErr = fmt.Errorf("error reading from %s: data is empty", fileName)
}
}()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not too big a fan of the usage of defer for handling errors. It makes the code difficult to read. Could we perhaps move this to the appropriate place?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I was on the fence on this one; the defer would reduce repeating the handling of these, but also keeps the complexity out of the part where we're handling the reading (i.e., it made the logic for the reading harder to read because it's now more about error-handling than the reading).

Abstracting it to a separate "decorate error" or otherwise function also felt a bit "too much", as it would split the logic to separate parts of the code.

I pushed 2 draft commits that remove the defer, which should be squashed if we decide to go for that; let me move this to draft so that we don't accidentally merge before deciding.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No strong feelings from my side, although the defer seems a bit cleaner indeed.

@thaJeztah thaJeztah force-pushed the refactor_secret_config_create branch from 5b9d046 to d0053b4 Compare March 11, 2025 11:23
@thaJeztah thaJeztah marked this pull request as draft March 11, 2025 11:27
Swarm has size constraints on the size of secrets, but the client-side would
read content into memory, regardless its size. This could lead to either the
client reading too much into memory, or it sending data that's larger than
the size limit of gRPC, which resulted in the error not being handled by
SwarmKit and a generic gRPC error returned.

Reading a secret from a file was added in [moby@c6f0b7f], which used a
system.OpenSequential for reading ([FILE_FLAG_SEQUENTIAL_SCAN]). While
there could be a very marginal benefit to prevent polluting the system's
cache (Windows won’t aggressively keep it in the cache, freeing up system
memory for other tasks). These details were not documented in code, and
possibly may be too marginal, but adding a comment to outline won't hurt
so this patch also adds a comment.

This patch:

- Rewrites readSecretData to not return a nil-error if no file was
  set, in stead only calling it when not using a driver.
- Implements reading the data with a limit-reader to prevent reading
  large files into memory.
- The limit is based on SwarmKits limits ([MaxSecretSize]), but made
  twice that size, just in case larger sizes are supported in future;
  the main goal is to have some constraints, and to prevent hitting
  the gRPC limit.
- Updates some error messages to include STDIN (when used), or the
  filename (when used).

Before this patch:

    ls -lh largefile
    -rw-------  1 thajeztah  staff   8.1M Mar  9 00:19 largefile

    docker secret create nosuchfile ./nosuchfile
    Error reading content from "./nosuchfile": open ./nosuchfile: no such file or directory

    docker secret create toolarge ./largefile
    Error response from daemon: rpc error: code = ResourceExhausted desc = grpc: received message larger than max (8462870 vs. 4194304)

    docker secret create empty ./emptyfile
    Error response from daemon: rpc error: code = InvalidArgument desc = secret data must be larger than 0 and less than 512000 bytes

    cat ./largefile | docker secret create toolarge -
    Error response from daemon: rpc error: code = ResourceExhausted desc = grpc: received message larger than max (8462870 vs. 4194304)

    cat ./emptyfile | docker secret create empty -
    Error response from daemon: rpc error: code = InvalidArgument desc = secret data must be larger than 0 and less than 512000 bytes

With this patch:

    docker secret create nosuchfile ./nosuchfile
    error reading from ./nosuchfile: open ./nosuchfile: no such file or directory

    docker secret create empty ./emptyfile
    error reading from ./emptyfile: data is empty

    docker secret create toolarge ./largefile
    Error response from daemon: rpc error: code = InvalidArgument desc = secret data must be larger than 0 and less than 512000 bytes

    cat ./largefile | docker secret create toolarge -
    Error response from daemon: rpc error: code = InvalidArgument desc = secret data must be larger than 0 and less than 512000 bytes

    cat ./emptyfile | docker secret create empty -
    error reading from STDIN: data is empty

[moby@c6f0b7f]: moby/moby@c6f0b7f
[FILE_FLAG_SEQUENTIAL_SCAN]: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea#FILE_FLAG_SEQUENTIAL_SCAN
[MaxSecretSize]: https://pkg.go.dev/github.com/moby/swarmkit/v2@v2.0.0-20250103191802-8c1959736554/api/validation#MaxSecretSize

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
Swarm has size constraints on the size of configs, but the client-side would
read content into memory, regardless its size. This could lead to either the
client reading too much into memory, or it sending data that's larger than
the size limit of gRPC, which resulted in the error not being handled by
SwarmKit and a generic gRPC error returned.

Reading a config from a file used a system.OpenSequential for reading
([FILE_FLAG_SEQUENTIAL_SCAN]). While there could be a very marginal benefit
to prevent polluting the system's cache (Windows won’t aggressively keep it
in the cache, freeing up system memory for other tasks). These details were
not documented in code, and possibly may be too marginal, but adding a comment
to outline won't hurt so this patch also adds a comment.

This patch:

- Factors out the reading code to a readConfigData, analogous to the
  equivalent in secret create.
- Implements reading the data with a limit-reader to prevent reading
  large files into memory.
- The limit is based on SwarmKits limits ([MaxConfigSize]), but made
  twice that size, just in case larger sizes are supported in future;
  the main goal is to have some constraints, and to prevent hitting
  the gRPC limit.
- Updates some error messages to include STDIN (when used), or the
  filename (when used).

Before this patch:

    ls -lh largefile
    -rw-------  1 thajeztah  staff   8.1M Mar  9 00:19 largefile

    docker config create nosuchfile ./nosuchfile
    Error reading content from "./nosuchfile": open ./nosuchfile: no such file or directory

    docker config create toolarge ./largefile
    Error response from daemon: rpc error: code = ResourceExhausted desc = grpc: received message larger than max (8462870 vs. 4194304)

    docker config create empty ./emptyfile
    Error response from daemon: rpc error: code = InvalidArgument desc = config data must be larger than 0 and less than 1024000 bytes

    cat ./largefile | docker config create toolarge -
    Error response from daemon: rpc error: code = ResourceExhausted desc = grpc: received message larger than max (8462870 vs. 4194304)

    cat ./emptyfile | docker config create empty -
    Error response from daemon: rpc error: code = InvalidArgument desc = config data must be larger than 0 and less than 1024000 bytes

With this patch:

    docker config create nosuchfile ./nosuchfile
    error reading from ./nosuchfile: open ./nosuchfile: no such file or directory

    docker config create empty ./emptyfile
    error reading from ./emptyfile: data is empty

    docker config create toolarge ./largefile
    Error response from daemon: rpc error: code = InvalidArgument desc = config data must be larger than 0 and less than 1024000 bytes

    cat ./largefile | docker config create toolarge -
    Error response from daemon: rpc error: code = InvalidArgument desc = secret data must be larger than 0 and less than 1024000 bytes

    cat ./emptyfile | docker config create empty -
    error reading from STDIN: data is empty

[FILE_FLAG_SEQUENTIAL_SCAN]: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea#FILE_FLAG_SEQUENTIAL_SCAN
[MaxConfigSize]: https://pkg.go.dev/github.com/moby/swarmkit/v2@v2.0.0-20250103191802-8c1959736554/manager/controlapi#MaxConfigSize

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
@thaJeztah thaJeztah force-pushed the refactor_secret_config_create branch from d0053b4 to d6d8ca6 Compare March 18, 2025 13:48
@thaJeztah thaJeztah marked this pull request as ready for review March 18, 2025 13:48
@thaJeztah
Copy link
Member Author

Rebased, and squashed the commits

@thaJeztah thaJeztah merged commit ecfdf74 into docker:master Mar 18, 2025
87 checks passed
@thaJeztah thaJeztah deleted the refactor_secret_config_create branch March 18, 2025 14:52
@thaJeztah thaJeztah added this to the 28.0.2 milestone Apr 7, 2025
@thaJeztah thaJeztah self-assigned this May 16, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/swarm kind/refactor PR's that refactor, or clean-up code status/2-code-review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants