package tenantfs import ( "crypto/sha256" "encoding/hex" "fmt" "os" "path/filepath" "strings" ) type Paths struct { Root string Database string Attachments string } func validTenantID(id string) bool { if !strings.HasPrefix(id, "tenant_") || len(id) != len("tenant_")+32 { return false } for _, char := range strings.TrimPrefix(id, "tenant_") { if !strings.ContainsRune("0123456789abcdef", char) { return false } } return true } func ensureRealDirectory(path string) error { if err := os.MkdirAll(path, 0o700); err != nil { return err } if err := os.Chmod(path, 0o700); err != nil { return err } info, err := os.Lstat(path) if err != nil { return err } if info.Mode()&os.ModeSymlink != 0 || !info.IsDir() { return fmt.Errorf("%s is not a real directory", path) } return nil } // Derive maps an internal tenant UUID to non-client-visible paths without // creating them. Restore tooling uses this to stage an atomic directory swap. func Derive(dataRoot, tenantID string) (Paths, error) { if !validTenantID(tenantID) { return Paths{}, fmt.Errorf("invalid tenant id") } root, err := filepath.Abs(strings.TrimSpace(dataRoot)) if err != nil || strings.TrimSpace(dataRoot) == "" { return Paths{}, fmt.Errorf("invalid data root") } tenantsRoot := filepath.Join(root, "tenants") digest := sha256.Sum256([]byte("nekonest-cloud/tenant-directory/v1\x00" + tenantID)) directoryName := hex.EncodeToString(digest[:16]) tenantRoot := filepath.Join(tenantsRoot, directoryName) if filepath.Dir(tenantRoot) != tenantsRoot { return Paths{}, fmt.Errorf("tenant path escaped root") } attachments := filepath.Join(tenantRoot, "attachments") return Paths{ Root: tenantRoot, Database: filepath.Join(tenantRoot, "relay.db"), Attachments: attachments, }, nil } // Resolve derives and creates the secure directory tree for a live Engine. // The caller still carries tenant ID separately for authorization and fencing; // filesystem paths never accept client-controlled path fragments. func Resolve(dataRoot, tenantID string) (Paths, error) { paths, err := Derive(dataRoot, tenantID) if err != nil { return Paths{}, err } root := filepath.Dir(filepath.Dir(paths.Root)) if err := ensureRealDirectory(root); err != nil { return Paths{}, fmt.Errorf("prepare data root: %w", err) } if err := ensureRealDirectory(filepath.Dir(paths.Root)); err != nil { return Paths{}, fmt.Errorf("prepare tenants root: %w", err) } if err := ensureRealDirectory(paths.Root); err != nil { return Paths{}, fmt.Errorf("prepare tenant root: %w", err) } if err := ensureRealDirectory(paths.Attachments); err != nil { return Paths{}, fmt.Errorf("prepare attachment root: %w", err) } return paths, nil }