Files
lab/bastion/src/labd/prisma/schema.prisma
Michal 04faa079e2 feat: v2.0 Phase 1 foundation — @lab/core, auth, RBAC, audit, resource store
New packages:
- @lab/core: Resource types, Output<T> (Pulumi), audit event types,
  auth types, environment/account types, resource kind registry

New Prisma schema (mcpctl pattern):
- User (email/password/bcrypt), Session (bearer tokens), Group, GroupMember
- ServiceAccount, RbacDefinition (JSON subjects + roleBindings)
- AuditEvent (correlation IDs, causal chains, fire-and-forget batching)
- Environment, Account (driver config, Infisical secret path), Binding
- Resource (generic, kind/name/env unique, origin/managedBy tracking)
- Secret, Fleet, FleetMember, GitSource
- Keeps v1.0 models: Server, Agent, Bastion, Cluster, JoinToken

New services:
- AuthService: bearer token login, bootstrap (first login creates admin),
  session management with 30-day expiry
- RbacService: environment-scoped permission checks, group membership,
  role hierarchy (admin > edit > view)
- AuditService: fire-and-forget event collection, batch 50 / flush 5s,
  correlation IDs for causal chains
- ResourceStore: CRUD with origin/managedBy, RBAC-enforced routes

New routes:
- POST /api/auth/login, POST /api/auth/logout (bearer token auth)
- GET/POST/PUT/DELETE /api/resources (RBAC-enforced CRUD)
- GET/POST /api/environments, GET/POST /api/accounts
- POST /api/accounts/bind, GET /api/bindings
- GET /api/events (audit query with --last, --kind, --env, --correlation)

New middleware:
- Bearer token auth (validates Authorization header, resolves user identity)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-02 01:42:28 +01:00

292 lines
8.1 KiB
Plaintext

generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "cockroachdb"
url = env("DATABASE_URL")
}
// ── Auth (mcpctl pattern: email/password + bearer token sessions) ──
model User {
id String @id @default(cuid())
email String @unique
password String // bcrypt
name String?
role UserRole @default(USER)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
sessions Session[]
auditLogs AuditEvent[]
groups GroupMember[]
}
enum UserRole {
USER
ADMIN
}
model Session {
id String @id @default(cuid())
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
token String @unique
expiresAt DateTime
createdAt DateTime @default(now())
@@index([userId])
@@index([token])
}
model Group {
id String @id @default(cuid())
name String @unique
description String?
createdAt DateTime @default(now())
members GroupMember[]
}
model GroupMember {
id String @id @default(cuid())
groupId String
group Group @relation(fields: [groupId], references: [id], onDelete: Cascade)
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([groupId, userId])
}
model ServiceAccount {
id String @id @default(cuid())
name String @unique
token String @unique
createdAt DateTime @default(now())
}
// ── RBAC (mcpctl pattern: named definitions with JSON subjects/bindings) ──
model RbacDefinition {
id String @id @default(cuid())
name String @unique
subjects Json // [{kind: "User"|"Group"|"ServiceAccount", name: string}]
roleBindings Json // [{role, resource, name?, environment?, action?}]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
// ── Audit (mcpctl pattern: fire-and-forget with correlation IDs) ──
model AuditEvent {
id String @id @default(cuid())
timestamp DateTime @default(now())
eventKind String
source String // cli | labd | agent | driver | fleet-controller | sync-controller
verified Boolean @default(false)
userId String?
user User? @relation(fields: [userId], references: [id])
userName String?
sessionId String?
environmentName String?
accountName String?
resourceKind String?
resourceName String?
correlationId String
parentEventId String?
details Json @default("{}")
result String // success | failure | denied | skipped
error String?
durationMs Int?
@@index([correlationId])
@@index([eventKind, timestamp])
@@index([environmentName, timestamp])
@@index([resourceKind, resourceName])
@@index([userId, timestamp])
}
// ── Core infrastructure ──
model Environment {
id String @id @default(cuid())
name String @unique
status String @default("active") // active | archived
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
bindings Binding[]
resources Resource[]
}
model Account {
id String @id @default(cuid())
name String @unique
driver String // baremetal-pxe | aws | gcp | kubernetes | ovh
config Json @default("{}")
// Credentials stored in Infisical, referenced by secretPath
secretPath String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
bindings Binding[]
resources Resource[]
}
model Binding {
id String @id @default(cuid())
environmentId String
environment Environment @relation(fields: [environmentId], references: [id], onDelete: Cascade)
accountId String
account Account @relation(fields: [accountId], references: [id], onDelete: Cascade)
@@unique([environmentId, accountId])
}
model Resource {
id String @id @default(cuid())
kind String
name String
environmentId String
environment Environment @relation(fields: [environmentId], references: [id])
accountId String
account Account @relation(fields: [accountId], references: [id])
origin String @default("cli") // file | cli | fleet | imported
managedBy String @default("manual") // gitops | manual | auto
sourceRef String?
desiredSpec Json @default("{}")
actualSpec Json?
platformRef String?
status String @default("pending") // pending | creating | ready | updating | deleting | error
statusMessage String?
lastReconciled DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([kind, name, environmentId])
@@index([environmentId])
@@index([accountId])
@@index([kind, status])
}
model Secret {
id String @id @default(cuid())
name String @unique
// Encrypted data — application-layer encryption as fallback if Infisical unavailable
data Json @default("{}")
version Int @default(1)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
// ── Fleet ──
model Fleet {
id String @id @default(cuid())
name String
environmentId String
accountId String
selector Json // fact-matching rules
onboardPipeline Json // step definitions
offboardPipeline Json?
approvalConfig Json?
status String @default("active")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
members FleetMember[]
}
model FleetMember {
id String @id @default(cuid())
fleetId String
fleet Fleet @relation(fields: [fleetId], references: [id], onDelete: Cascade)
serverId String
status String // discovered | pending | onboarding | active | offboarding | removed
joinedAt DateTime @default(now())
@@index([fleetId])
}
// ── Git sources (for sync controller) ──
model GitSource {
id String @id @default(cuid())
name String @unique
repo String
branch String @default("main")
path String @default("environments/")
lastSync DateTime?
createdAt DateTime @default(now())
}
// ── Existing v1.0 models (kept for bastion/agent compatibility) ──
model Server {
id String @id @default(uuid())
hostname String @unique
mac String? @unique
cloud String @default("baremetal")
environment String @default("default")
role String @default("worker")
labels Json @default("{}")
ip String?
agentVersion String?
status String @default("unknown")
lastHeartbeat DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
agent Agent?
}
model Agent {
id String @id @default(uuid())
serverId String @unique
server Server @relation(fields: [serverId], references: [id], onDelete: Cascade)
certificatePem String?
enrolledAt DateTime @default(now())
lastSeen DateTime?
facts Json? // hardware facts reported by agent
@@index([serverId])
}
model JoinToken {
id String @id @default(uuid())
token String @unique
type String @default("one-time")
label String?
usedBy String?
usedAt DateTime?
revokedAt DateTime?
createdAt DateTime @default(now())
expiresAt DateTime?
}
model Bastion {
id String @id @default(uuid())
hostname String @unique
network String
serverIp String
status String @default("offline")
lastHeartbeat DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Cluster {
id String @id @default(uuid())
name String @unique
cloud String @default("baremetal")
environment String @default("default")
kubeconfigEnc String?
labels Json @default("{}")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}