Close all 14 findings from the codebase audit (1 high, 5 medium, 8 low): - Locking: replace the global API and workspace mutexes with per-instance and per-user keyed locks (new internal/api/locks.go) so a slow restore, upgrade or transfer no longer blocks unrelated users or instances; move Docker I/O out of the build queue and invalidate concurrent image-index observations with a generation counter; serialize instance deletion behind in-flight creation. - Security: equalize login timing for unknown usernames, periodically purge expired sessions/refresh tokens/lock rows, URL-encode orphan IDs in the frontend, upgrade vitest 3.x->5.x to clear 3 npm audit vulnerabilities (2 critical RCE). - Deployment: add a -healthcheck mode probing /healthz (used by Dockerfile HEALTHCHECK and compose), make ADMIN_PASSWORD optional in compose with removal guidance after first start, exclude test scripts from the image, and cap panel resources (cpus: 2, pids_limit: 512). - Reliability: check rows.Err() on admin listing loops, surface intent polling errors without sticking, disable close during in-flight create. Verified: go build/vet, full go test -race -count=1 ./..., frontend tsc build + 32 tests, npm audit 0, compose config, real-container healthcheck.
95 lines
2.4 KiB
Go
95 lines
2.4 KiB
Go
package api
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"sync"
|
|
"testing"
|
|
)
|
|
|
|
func TestProfileQuotaReplacement(t *testing.T) {
|
|
if os.Geteuid() != 0 {
|
|
t.Skip("requires chown")
|
|
}
|
|
a, i, home := configTestAPI(t, false)
|
|
profile := filepath.Join(home, "profiles/web/cordis.patch.yml")
|
|
if err := os.MkdirAll(filepath.Dir(profile), 0700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
configWrite(t, profile, "foo: old")
|
|
if _, err := a.s.DB.Exec("UPDATE users SET quota=8 WHERE id=?", i.UID); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, tc := range []struct {
|
|
body string
|
|
code int
|
|
}{
|
|
{`{"content":"foo: biggg"}`, 413},
|
|
{`{"content":"foo: new"}`, 200},
|
|
{`{"content":"foo: x"}`, 200},
|
|
} {
|
|
w := configCall(a, i, "PUT", "profile", tc.body)
|
|
if w.Code != tc.code {
|
|
t.Fatalf("%d %s", w.Code, w.Body.String())
|
|
}
|
|
}
|
|
data, err := os.ReadFile(profile)
|
|
if err != nil || string(data) != "foo: x" {
|
|
t.Fatalf("%q %v", data, err)
|
|
}
|
|
}
|
|
|
|
func TestSnapshotQuotaIncludesResourcesAndExistingSnapshots(t *testing.T) {
|
|
a, i, home := configTestAPI(t, false)
|
|
configWrite(t, filepath.Join(home, "state"), "1234")
|
|
if err := os.MkdirAll(filepath.Join(filepath.Dir(home), "pre-restore-old"), 0700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
configWrite(t, filepath.Join(filepath.Dir(home), "pre-restore-old/state"), "1234")
|
|
root, err := os.OpenRoot(filepath.Dir(home))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer root.Close()
|
|
if _, err = a.s.DB.Exec("UPDATE users SET quota=15 WHERE id=?", i.UID); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err = a.quotaSnapshot(i.UID, root, 4); err == nil {
|
|
t.Fatal("resource bytes omitted")
|
|
}
|
|
if size, err := a.quotaSnapshot(i.UID, root, 3); err != nil || size != 4 {
|
|
t.Fatalf("size=%d err=%v", size, err)
|
|
}
|
|
if _, err := root.Lstat("pre-restore-old/state"); err != nil {
|
|
t.Fatal("existing snapshot changed")
|
|
}
|
|
}
|
|
|
|
func TestQuotaSerializedWriters(t *testing.T) {
|
|
a, i, home := configTestAPI(t, false)
|
|
if _, err := a.s.DB.Exec("UPDATE users SET quota=4 WHERE id=?", i.UID); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var wg sync.WaitGroup
|
|
accepted := 0
|
|
for _, name := range []string{"one", "two"} {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
user := a.userLocks.Acquire(i.UID)
|
|
defer user.Release()
|
|
if a.quotaGrowth(i.UID, 4) == nil {
|
|
if err := os.WriteFile(filepath.Join(home, name), []byte("1234"), 0600); err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
accepted++
|
|
}
|
|
}()
|
|
}
|
|
wg.Wait()
|
|
if accepted != 1 {
|
|
t.Fatalf("accepted %d writes", accepted)
|
|
}
|
|
}
|