RC-9-A: Bugfix
All checks were successful
release-tag / release-image (push) Successful in 4m41s

This commit is contained in:
2026-08-11 20:33:43 +02:00
parent 1f61989c2d
commit eefe6dd415
4 changed files with 64 additions and 8 deletions

View File

@@ -473,3 +473,10 @@ without the application's external Go dependencies:
```bash
go test ./internal/customer/docker.go ./internal/customer/docker_test.go
```
## V4.2.5 worker identity-volume migration check
After publishing the new worker image, start a hosted worker that already owns
an identity volume from an older release. It must start without `permission
denied`, keep the same client identity, and remain writable at
`/identity/identity.json`. Do not delete the named volume for this test.

View File

@@ -0,0 +1,24 @@
# V4.2.5 Worker identity volume permission fix
Managed workers persist `/identity/identity.json` in a Docker named volume. A
new named volume is normally root-owned, while identities from older releases
may already be mode `0600` and owned by a different UID. With the hardened
worker capability set this could make the client fail immediately with:
```
open /identity/identity.json: permission denied
```
V4.2.5 makes the worker image normalize only `/identity` and the fixed
`/identity/identity.json` path before startup. The entrypoint starts as root,
fixes ownership, and immediately executes `/app/neuralhunt-client` as the
unprivileged `app` user via `su-exec`.
Customer Service still drops all Linux capabilities and adds back only the four
bootstrap capabilities needed by that entrypoint: `CHOWN`, `DAC_OVERRIDE`,
`SETUID`, and `SETGID`. Workers still receive no Docker socket and retain a
read-only root filesystem.
Existing worker identity volumes are intentionally kept. Rebuilding/pulling the
worker image and restarting an affected worker is sufficient; deleting the
worker/volume would destroy its identity and is not required.

View File

@@ -0,0 +1,22 @@
#!/bin/sh
set -eu
# Docker named volumes are normally created as root:root. Older Neural Hunt
# worker versions could also leave identity.json owned by a different UID.
# Normalize only the dedicated identity mount and the fixed identity file, then
# permanently drop to the unprivileged app user before the Go client starts.
if [ "$(id -u)" = "0" ]; then
if [ -L /identity/identity.json ]; then
echo "refusing symlink at /identity/identity.json" >&2
exit 1
fi
chown app:app /identity
if [ -e /identity/identity.json ]; then
chown app:app /identity/identity.json
fi
exec su-exec app:app /app/neuralhunt-client "$@"
fi
exec /app/neuralhunt-client "$@"

View File

@@ -210,10 +210,10 @@ func (d *DockerClient) CreateWorker(ctx context.Context, c WorkerContainerConfig
name := url.QueryEscape(c.Name)
body := map[string]any{
"Image": c.Image,
// Named Docker volumes are root-owned when first mounted. Managed workers
// therefore run uid 0 only inside their own locked-down container so they
// can create the 0600 identity file. They receive no Docker socket, all
// Linux capabilities are dropped and the image root filesystem is read-only.
// Named Docker volumes are root-owned when first mounted, and identities
// created by older releases may be owned by a different UID. The dedicated
// worker image starts as uid 0 only for its tiny ownership-normalization
// entrypoint and immediately drops to the unprivileged app user.
"User": "0:0",
"Cmd": []string{"-url", c.GameURL, "-identity", "/identity/identity.json", "-non-interactive", "-quiet", "-task", c.TaskID, "-beacon-path", c.BeaconPath},
"Env": []string{
@@ -228,10 +228,13 @@ func (d *DockerClient) CreateWorker(ctx context.Context, c WorkerContainerConfig
"NetworkMode": c.Network,
"ReadonlyRootfs": true,
"CapDrop": []string{"ALL"},
"SecurityOpt": []string{"no-new-privileges"},
"PidsLimit": 128,
"Memory": 256 * 1024 * 1024,
"NanoCpus": int64(1_000_000_000),
// Bootstrap-only capabilities: the image entrypoint fixes ownership of
// /identity and then su-exec permanently switches to uid/gid app.
"CapAdd": []string{"CHOWN", "DAC_OVERRIDE", "SETUID", "SETGID"},
"SecurityOpt": []string{"no-new-privileges"},
"PidsLimit": 128,
"Memory": 256 * 1024 * 1024,
"NanoCpus": int64(1_000_000_000),
},
}
// A dedicated worker image already declares /app/neuralhunt-client as its