From 1037aeb6cd9e8714184832a7c28173414f346e12 Mon Sep 17 00:00:00 2001 From: Antoine Toussaint <312536060+antoinetoussaint-byte@users.noreply.github.com> Date: Tue, 27 Oct 2026 16:33:55 -0400 Subject: [PATCH 1/3] fix(runtime): bind MinIO and gateway to their exact parent service --- README.md | 13 +++++++++++++ go.mod | 8 ++++++-- go.sum | 16 ++++++++-------- minio_custody_e2e_test.go | 10 ++++++++++ runtime.go | 6 ++++++ runtime_e2e_test.go | 17 +++++++++++++++++ 6 files changed, 60 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index fbbe0f3..2ba6058 100644 --- a/README.md +++ b/README.md @@ -398,6 +398,19 @@ closed and require recovery. `SOS_LOCAL_MINIO_INITIALIZE` is no longer read. See [local custody and retained-volume recovery](docs/local-minio-recovery.md) for ownership, paths, lifecycle behavior and recovery instructions. +Both local MinIO and gateway containers bind the loaded parent service through +Core's `DockerEnvironment.WithServiceIdentity`. Their `codefly.service` label +records the exact module/service, independently of their `-minio` and `-gateway` +names. The CLI reads Core's shared attribution parser in addition to its scope, +name and port checks. A conflicting present parent label refuses adoption or +replacement before runtime configuration drift can remove a container. + +`Stop` keeps the containers available; a new runtime instance may replace them +to rotate credentials while the owned MinIO data survives. Containers created +by older agents have no parent label and remain unproven to the CLI's suffixed +container check. Installing a new CLI does not relabel them; recovery follows +the explicit retained-volume procedure above, without broad cleanup. + ## Authentication The gateway holds the backend credentials and exposes bucket-wide read, write, diff --git a/go.mod b/go.mod index 780e924..3fbb329 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.23.14 github.com/aws/aws-sdk-go-v2/service/s3 v1.114.3 github.com/aws/smithy-go v1.28.5 - github.com/codefly-dev/core v0.19.5 + github.com/codefly-dev/core v0.19.8-0.20261027203111-6eadd92dd156 github.com/containerd/errdefs v1.0.0 github.com/docker/docker v28.5.2+incompatible github.com/gofrs/flock v0.13.1 @@ -47,7 +47,7 @@ require ( github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.57.0 // indirect github.com/Masterminds/semver v1.5.0 // indirect github.com/Masterminds/semver/v3 v3.5.0 // indirect - github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/Microsoft/go-winio v0.6.3-0.20251027160822-ad3df93bed29 // indirect github.com/VividCortex/ewma v1.2.0 // indirect github.com/antlr4-go/antlr/v4 v4.13.1 // indirect github.com/apache/arrow-go/v18 v18.7.0 // indirect @@ -72,6 +72,7 @@ require ( github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 // indirect github.com/codefly-dev/gortk v0.2.0 // indirect github.com/containerd/errdefs/pkg v0.3.0 // indirect + github.com/containerd/log v0.2.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/distribution/reference v0.6.0 // indirect github.com/docker/go-connections v0.8.2 // indirect @@ -132,7 +133,9 @@ require ( github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/patternmatcher v0.6.1 // indirect + github.com/moby/sys/sequential v0.7.0 // indirect github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect + github.com/morikuni/aec v1.1.0 // indirect github.com/oklog/ulid/v2 v2.1.2 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect @@ -159,6 +162,7 @@ require ( go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.72.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.71.0 // indirect go.opentelemetry.io/otel v1.47.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.47.0 // indirect go.opentelemetry.io/otel/log v1.47.0 // indirect go.opentelemetry.io/otel/metric v1.47.0 // indirect go.opentelemetry.io/otel/sdk v1.47.0 // indirect diff --git a/go.sum b/go.sum index d6f7c24..9a11534 100644 --- a/go.sum +++ b/go.sum @@ -60,8 +60,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/semver/v3 v3.5.0 h1:kQceYJfbupGfZOKZQg0kou0DgAKhzDg2NZPAwZ/2OOE= github.com/Masterminds/semver/v3 v3.5.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= -github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= -github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/Microsoft/go-winio v0.6.3-0.20251027160822-ad3df93bed29 h1:0kQAzHq8vLs7Pptv+7TxjdETLf/nIqJpIB4oC6Ba4vY= +github.com/Microsoft/go-winio v0.6.3-0.20251027160822-ad3df93bed29/go.mod h1:ZWa7ssZJT30CCDGJ7fk/2SBTq9BIQrrVjrcss0UW2s0= github.com/ProtonMail/go-crypto v1.4.1 h1:9RfcZHqEQUvP8RzecWEUafnZVtEvrBVL9BiF67IQOfM= github.com/ProtonMail/go-crypto v1.4.1/go.mod h1:e1OaTyu5SYVrO9gKOEhTc+5UcXtTUa+P3uLudwcgPqo= github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= @@ -130,16 +130,16 @@ github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 h1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik= github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4= -github.com/codefly-dev/core v0.19.5 h1:Ikf4NjSPU995xvCjXRtvevgB9GHIBhGnFEVDz1XjewA= -github.com/codefly-dev/core v0.19.5/go.mod h1:0uX/7lTBVaGV0RI2Mb3mpEr/PPdx/jZqXHym/U8FVDI= +github.com/codefly-dev/core v0.19.8-0.20261027203111-6eadd92dd156 h1:OLQ/LG4+KGRz3crTc8zgQlxTS/Ta3Yh7OUFu+12N5u4= +github.com/codefly-dev/core v0.19.8-0.20261027203111-6eadd92dd156/go.mod h1:NB7x1UZ01rYTfEZEl4vUFD1A+fgOXhsWbgA3M8x4ThA= github.com/codefly-dev/gortk v0.2.0 h1:7bOlS5valYz2zil+fZctQNcPCYBcPj86abcw9N8h1hQ= github.com/codefly-dev/gortk v0.2.0/go.mod h1:dDWUMFgAP063OCGhTEpV7FFUj9+zTcxxO/nV80VKEwg= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= -github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= -github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/containerd/log v0.2.0 h1:BewD/umNgVnoczglOpX8eRMyEy5t5iPlu5AIpnWDONc= +github.com/containerd/log v0.2.0/go.mod h1:/M7L7CXKcPTfNC74XzaK+5H5KbO5+4lJVpuVI6vRLoM= github.com/cyphar/filepath-securejoin v0.7.0 h1:s0Y3ITPy6sQn5xt54DuYvTF8hu134ooYLUb58DX/HjE= github.com/cyphar/filepath-securejoin v0.7.0/go.mod h1:ymLGms/u3BYaviIiuKFnUx8EkQEZeK6cInNoAPJA3o4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -412,8 +412,8 @@ go.opentelemetry.io/otel v1.47.0 h1:j7ALJ/zgkS7Z6aeJW09p8VC9804bC+PpeTfCD4XPnOM= go.opentelemetry.io/otel v1.47.0/go.mod h1:8wS9O2qfXrYrzp6hIF/HOYJJf/wIhFPhR2xLuP+iXQU= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.47.0 h1:julhjPeUH/q/7hinbSdDdqt5h7Zw9YWmRlWRhI0jd54= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.47.0/go.mod h1:Ao2mz688LH/tFf0yMAenidq6k2YNSx6SIY2q6jDACck= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.45.0 h1:QBajQ2SrwQijzHyZbQlPsuIzpl/ll8DY6wPWsajeGcI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.45.0/go.mod h1:08ZQLjrPLQ6R4kAXvuOvODEer5Yh4CoFvll5qB2BCI8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.47.0 h1:aXZXsZ012wOgVkqaiQv+Z/d8V0xKjmg70F6m4CE94lc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.47.0/go.mod h1:ziu1gUIJhAaxM3EV1e3Ralk5AAyL1UOYz3lJJ4VSI38= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.45.0 h1:dm9iyzn6tioYZtwqaiBSU0TSI8Yu/8dTIbfG0+B49DY= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.45.0/go.mod h1:xAvxYjYK28qvt+yu4BYZ/zMmAjwMXINXD6JiMyeB8iI= go.opentelemetry.io/otel/log v1.47.0 h1:cOTS1CcLbSQeZKanGJ+0JpF/+t4PELi3O3bbl2lqCcI= diff --git a/minio_custody_e2e_test.go b/minio_custody_e2e_test.go index e62c71f..6f1266e 100644 --- a/minio_custody_e2e_test.go +++ b/minio_custody_e2e_test.go @@ -64,6 +64,10 @@ func TestMinIOPersistentCustody(t *testing.T) { firstID, err := rt.minioEnv.ContainerID() require.NoError(t, err) requireMinIOFilesystemOwner(t, rt, firstID) + requireContainerParentService(t, rt, firstID) + firstGatewayID, err := rt.gatewayEnv.ContainerID() + require.NoError(t, err) + requireContainerParentService(t, rt, firstGatewayID) firstPassword, firstToken := rt.minioPassword, rt.gatewayToken keys := []string{"sources/handbook/original.txt", "sources/handbook/nested/bytes.bin"} payloads := [][]byte{[]byte("persistent source bytes\n"), bytes.Repeat([]byte{0, 1, 255, 42}, 32768)} @@ -132,6 +136,12 @@ func TestMinIOPersistentCustody(t *testing.T) { secondID, err := rt.minioEnv.ContainerID() require.NoError(t, err) requireMinIOFilesystemOwner(t, rt, secondID) + requireContainerParentService(t, rt, secondID) + secondGatewayID, err := rt.gatewayEnv.ContainerID() + require.NoError(t, err) + requireContainerParentService(t, rt, secondGatewayID) + require.NotEqual(t, firstGatewayID, secondGatewayID) + requireContainerRemoved(t, firstGatewayID) require.NotEqual(t, firstID, secondID) require.True(t, firstPassword != rt.minioPassword, "MinIO credentials must rotate") require.True(t, firstToken != rt.gatewayToken, "gateway credentials must rotate") diff --git a/runtime.go b/runtime.go index feaeb0d..3a3842f 100644 --- a/runtime.go +++ b/runtime.go @@ -276,6 +276,9 @@ func (s *Runtime) startLocalMinIOWithRelease(ctx context.Context) (unlock func() // Own the environment before starting it: a container that is created and // then exits still has to be removed, and only Shutdown does that. s.minioEnv = runner + if err := runner.WithServiceIdentity(s.Identity); err != nil { + return nil, s.Wool.Wrapf(err, "cannot bind minio parent service") + } runner.WithOutput(os.Stdout) // The published port is for the agent alone (bucket bootstrap, below) and an // operator's recovery tools, so it stays on core's loopback default. The @@ -521,6 +524,9 @@ func (s *Runtime) startGateway(ctx context.Context, hostPort uint16) error { // Own the environment before starting it: a container that is created and // then exits still has to be removed, and only Shutdown does that. s.gatewayEnv = runner + if err := runner.WithServiceIdentity(s.Identity); err != nil { + return s.Wool.Wrapf(err, "cannot bind gateway parent service") + } runner.WithOutput(os.Stdout) runner.WithUser(gatewayContainerUser) runner.WithPortMapping(ctx, hostPort, gatewayContainerPort) diff --git a/runtime_e2e_test.go b/runtime_e2e_test.go index c1a1735..e39033f 100644 --- a/runtime_e2e_test.go +++ b/runtime_e2e_test.go @@ -106,6 +106,8 @@ func TestRuntimeEndToEndPutGet(t *testing.T) { require.NoError(t, err) requireMinIOFilesystemOwner(t, rt, minioID) requirePublishedOnLoopbackOnly(t, minioID, minioContainerPort) + requireContainerParentService(t, rt, minioID) + requireContainerParentService(t, rt, gatewayID) conf, err := resources.ExtractConfiguration(init.RuntimeConfigurations, resources.NewRuntimeContextNative()) require.NoError(t, err) @@ -186,6 +188,21 @@ func TestRuntimeEndToEndPutGet(t *testing.T) { require.Error(t, exec.Command("docker", "network", "inspect", network).Run(), "network %s survived teardown", network) } +// requireContainerParentService checks the same Docker metadata the CLI reads, +// through Core's public parser; suffixes are never used as ownership evidence. +func requireContainerParentService(t *testing.T, rt *Runtime, containerID string) { + t.Helper() + out, err := exec.Command("docker", "inspect", "-f", `{{json .Config.Labels}}`, containerID).Output() + require.NoError(t, err) + var labels map[string]string + require.NoError(t, json.Unmarshal(out, &labels)) + parent, err := dockerrun.ContainerServiceIdentity(labels) + require.NoError(t, err) + require.NotNil(t, parent) + require.Equal(t, rt.Identity.Module, parent.Module) + require.Equal(t, rt.Identity.Name, parent.Name) +} + // requireContainerRemoved asserts the container no longer exists on the daemon. func requireContainerRemoved(t *testing.T, containerID string) { t.Helper() From 04a9a9d5f59cda51f71bec99bb84b62e839d6d5e Mon Sep 17 00:00:00 2001 From: Antoine Toussaint <312536060+antoinetoussaint-byte@users.noreply.github.com> Date: Tue, 27 Oct 2026 16:42:14 -0400 Subject: [PATCH 2/3] test(runtime): assert readiness during retained-data reuse --- minio_custody_e2e_test.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/minio_custody_e2e_test.go b/minio_custody_e2e_test.go index 6f1266e..748f745 100644 --- a/minio_custody_e2e_test.go +++ b/minio_custody_e2e_test.go @@ -46,8 +46,9 @@ func TestMinIOPersistentCustody(t *testing.T) { response, err := rt.Init(ctx, &runtimev0.InitRequest{RuntimeContext: runtimeContext, ProposedNetworkMappings: mappings}) require.NoError(t, err) require.NotEqual(t, runtimev0.InitStatus_ERROR, response.GetStatus().GetState(), response.GetStatus().GetMessage()) - _, err = rt.Start(ctx, &runtimev0.StartRequest{}) + started, err := rt.Start(ctx, &runtimev0.StartRequest{}) require.NoError(t, err) + require.Equal(t, runtimev0.StartStatus_STARTED, started.GetStatus().GetState(), started.GetStatus().GetMessage()) } connect := func(token string) storagev0.ObjectStorageClient { endpoint, err := rt.gatewayEnv.ContainerID() @@ -112,8 +113,9 @@ func TestMinIOPersistentCustody(t *testing.T) { } _, err = rt.Stop(ctx, &runtimev0.StopRequest{}) require.NoError(t, err) - _, err = rt.Start(ctx, &runtimev0.StartRequest{}) + started, err := rt.Start(ctx, &runtimev0.StartRequest{}) require.NoError(t, err) + require.Equal(t, runtimev0.StartStatus_STARTED, started.GetStatus().GetState(), started.GetStatus().GetMessage()) verify() // Also exercise a real Docker Stop/Start on only this test's MinIO. require.NoError(t, exec.Command("docker", "stop", firstID).Run()) From f9134954915677f8cae6c90fa7e16fa7bcea3853 Mon Sep 17 00:00:00 2001 From: Antoine Toussaint <312536060+antoinetoussaint-byte@users.noreply.github.com> Date: Tue, 27 Oct 2026 17:19:26 -0400 Subject: [PATCH 3/3] build(deps): qualify durable Core process ownership --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 3fbb329..7de222c 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.23.14 github.com/aws/aws-sdk-go-v2/service/s3 v1.114.3 github.com/aws/smithy-go v1.28.5 - github.com/codefly-dev/core v0.19.8-0.20261027203111-6eadd92dd156 + github.com/codefly-dev/core v0.19.8-0.20261027211051-bc4e15cab5b0 github.com/containerd/errdefs v1.0.0 github.com/docker/docker v28.5.2+incompatible github.com/gofrs/flock v0.13.1 diff --git a/go.sum b/go.sum index 9a11534..d8dc547 100644 --- a/go.sum +++ b/go.sum @@ -130,8 +130,8 @@ github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 h1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik= github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4= -github.com/codefly-dev/core v0.19.8-0.20261027203111-6eadd92dd156 h1:OLQ/LG4+KGRz3crTc8zgQlxTS/Ta3Yh7OUFu+12N5u4= -github.com/codefly-dev/core v0.19.8-0.20261027203111-6eadd92dd156/go.mod h1:NB7x1UZ01rYTfEZEl4vUFD1A+fgOXhsWbgA3M8x4ThA= +github.com/codefly-dev/core v0.19.8-0.20261027211051-bc4e15cab5b0 h1:r5xxRusbZ54Wpk2T6aqqqNWvHmVVJ8HMJNdofE7mqrc= +github.com/codefly-dev/core v0.19.8-0.20261027211051-bc4e15cab5b0/go.mod h1:NB7x1UZ01rYTfEZEl4vUFD1A+fgOXhsWbgA3M8x4ThA= github.com/codefly-dev/gortk v0.2.0 h1:7bOlS5valYz2zil+fZctQNcPCYBcPj86abcw9N8h1hQ= github.com/codefly-dev/gortk v0.2.0/go.mod h1:dDWUMFgAP063OCGhTEpV7FFUj9+zTcxxO/nV80VKEwg= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=