diff --git a/go.mod b/go.mod index 4ba9eec1..c313cb71 100644 --- a/go.mod +++ b/go.mod @@ -30,7 +30,8 @@ require ( github.com/teambition/rrule-go v1.8.2 github.com/testcontainers/testcontainers-go v0.42.0 github.com/testcontainers/testcontainers-go/modules/postgres v0.42.0 - go.kenn.io/kit v0.20.0 + go.kenn.io/kit v0.21.3 + golang.org/x/sync v0.21.0 golang.org/x/sys v0.46.0 golang.org/x/term v0.44.0 golang.org/x/text v0.38.0 @@ -167,7 +168,6 @@ require ( golang.org/x/mod v0.36.0 // indirect golang.org/x/net v0.56.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect - golang.org/x/sync v0.21.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260706201446-f0a921348800 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260706201446-f0a921348800 // indirect google.golang.org/grpc v1.81.1 // indirect diff --git a/go.sum b/go.sum index 0482caba..e4b60be1 100644 --- a/go.sum +++ b/go.sum @@ -285,8 +285,8 @@ github.com/yuin/goldmark-emoji v1.0.6 h1:QWfF2FYaXwL74tfGOW5izeiZepUDroDJfWubQI9 github.com/yuin/goldmark-emoji v1.0.6/go.mod h1:ukxJDKFpdFb5x0a5HqbdlcKtebh086iJpI31LTKmWuA= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= -go.kenn.io/kit v0.20.0 h1:vviC9r/QRWN7Sh09ISh+7xlYVoKvCKVoqFLcUkGutL4= -go.kenn.io/kit v0.20.0/go.mod h1:Cg1V5dG+XaSDYr/nE0tvmCxotfpYtW1vPhyc+Ph9Ny0= +go.kenn.io/kit v0.21.3 h1:7LejpspYJR2Lb2mCQnukXW5wc2cLQ9P6G2UInaoeowI= +go.kenn.io/kit v0.21.3/go.mod h1:sUFJe7d25a3FYwjL3mlHx/qzHhF8//xNnR920jzoejw= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/bridges/prometheus v0.69.0 h1:saQoWg5845Q8TojpqeVStS7zGwVZ6bc5W2PJavTPiBM= diff --git a/internal/client/ensure.go b/internal/client/ensure.go index e3d115c1..2ace4e9c 100644 --- a/internal/client/ensure.go +++ b/internal/client/ensure.go @@ -82,7 +82,9 @@ var ( stopRunningDaemonsForEnsure = stopRunningDaemons signalDaemonStopForEnsure = daemon.SignalDaemonStop discoverDaemonForAutoStart = discoverForEnsureWithError - checkDaemonStateForEnsure = checkDaemonStateWritable + checkDaemonStateForEnsure = func(dataDir string) error { + return (kitdaemon.RuntimeStore{Dir: dataDir}).CheckWritable() + } ) // EnsureRunning returns a live daemon's base URL, auto-starting the daemon @@ -328,22 +330,6 @@ func autoStart(ctx context.Context, dataDir string) (string, error) { return "", fmt.Errorf("daemon failed to start within %s; inspect kata daemon status and kata daemon logs", daemonStartupWait) } -// checkDaemonStateWritable verifies that an auto-started daemon can write its -// runtime record before spawning it. Without this check, a sandboxed child can -// fail silently and leave the caller waiting for the full readiness deadline. -func checkDaemonStateWritable(dataDir string) error { - if err := os.MkdirAll(dataDir, 0o700); err != nil { - return err - } - f, err := os.CreateTemp(dataDir, ".kata-write-check-*") - if err != nil { - return err - } - path := f.Name() - defer func() { _ = os.Remove(path) }() - return f.Close() -} - // daemonLogWriter opens /daemon.log for the auto-started daemon's // stdout+stderr. Returns nil (so exec falls back to the null device) if the // directory or file cannot be created — the caller must never substitute its diff --git a/internal/client/ensure_unix_test.go b/internal/client/ensure_unix_test.go new file mode 100644 index 00000000..fc475ec5 --- /dev/null +++ b/internal/client/ensure_unix_test.go @@ -0,0 +1,38 @@ +//go:build !windows + +package client + +import ( + "context" + "errors" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + kitdaemon "go.kenn.io/kit/daemon" +) + +func TestAutoStartRejectsSymlinkStateDirectoryBeforeSpawn(t *testing.T) { + base := t.TempDir() + target := filepath.Join(base, "target") + require.NoError(t, os.Mkdir(target, 0o700)) + dataDir := filepath.Join(base, "runtime") + require.NoError(t, os.Symlink(target, dataDir)) + + originalStart := startDetachedDaemonForEnsure + spawnErr := errors.New("unexpected daemon spawn") + started := false + startDetachedDaemonForEnsure = func(context.Context, kitdaemon.StartDetachedOptions) error { + started = true + return spawnErr + } + t.Cleanup(func() { startDetachedDaemonForEnsure = originalStart }) + + _, err := autoStart(t.Context(), dataDir) + + require.Error(t, err) + assert.False(t, started, "daemon should not be spawned for a symlinked state directory") + assert.NotErrorIs(t, err, spawnErr) +}