Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
8 changes: 6 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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.20261027211051-bc4e15cab5b0
github.com/containerd/errdefs v1.0.0
github.com/docker/docker v28.5.2+incompatible
github.com/gofrs/flock v0.13.1
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
16 changes: 8 additions & 8 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down Expand Up @@ -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.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=
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=
Expand Down Expand Up @@ -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=
Expand Down
16 changes: 14 additions & 2 deletions minio_custody_e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -64,6 +65,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)}
Expand Down Expand Up @@ -108,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())
Expand All @@ -132,6 +138,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")
Expand Down
6 changes: 6 additions & 0 deletions runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
17 changes: 17 additions & 0 deletions runtime_e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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()
Expand Down
Loading