Scaffold dd0c/drift SaaS backend: Fastify, RLS, ingestion, dashboard API

- Fastify server with Zod validation, pino logging, CORS/helmet
- Drift report ingestion endpoint with nonce replay prevention
- Dashboard API: stacks list, drift history, report detail, summary stats
- PostgreSQL schema with RLS: tenants, users, agent_keys, drift_reports, remediation_actions
- withTenant() helper for safe connection pool tenant context management
- Config via Zod-validated env vars
This commit is contained in:
2026-03-01 02:45:33 +00:00
parent 31cb36fb77
commit e67cef518e
9 changed files with 486 additions and 0 deletions

View File

@@ -0,0 +1,115 @@
-- dd0c/drift V1 schema — PostgreSQL with RLS
-- Enable RLS
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- Tenants
CREATE TABLE tenants (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
name TEXT NOT NULL,
slug TEXT NOT NULL UNIQUE,
tier TEXT NOT NULL DEFAULT 'free' CHECK (tier IN ('free', 'pro', 'enterprise')),
stripe_customer_id TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- Users
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
email TEXT NOT NULL,
name TEXT,
role TEXT NOT NULL DEFAULT 'member' CHECK (role IN ('owner', 'member', 'viewer')),
github_id BIGINT UNIQUE,
avatar_url TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX idx_users_tenant ON users(tenant_id);
-- Agent API keys (mTLS + bearer)
CREATE TABLE agent_keys (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
key_prefix CHAR(8) NOT NULL,
key_hash TEXT NOT NULL,
name TEXT NOT NULL DEFAULT 'Default Agent',
cert_fingerprint TEXT, -- mTLS cert SHA256
revoked_at TIMESTAMPTZ,
last_seen_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE UNIQUE INDEX idx_agent_keys_prefix ON agent_keys(key_prefix) WHERE revoked_at IS NULL;
-- Drift reports (core table)
CREATE TABLE drift_reports (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
stack_name TEXT NOT NULL,
stack_fingerprint TEXT NOT NULL,
agent_version TEXT NOT NULL,
scanned_at TIMESTAMPTZ NOT NULL,
state_serial BIGINT NOT NULL,
lineage TEXT NOT NULL,
total_resources INT NOT NULL,
drift_score NUMERIC(5,2) NOT NULL DEFAULT 0,
nonce TEXT NOT NULL UNIQUE,
raw_report JSONB NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX idx_drift_reports_tenant_stack ON drift_reports(tenant_id, stack_name, scanned_at DESC);
CREATE INDEX idx_drift_reports_score ON drift_reports(tenant_id, drift_score) WHERE drift_score > 0;
-- Remediation actions
CREATE TABLE remediation_actions (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
report_id UUID NOT NULL REFERENCES drift_reports(id) ON DELETE CASCADE,
stack_name TEXT NOT NULL,
action_type TEXT NOT NULL CHECK (action_type IN ('plan', 'apply', 'accept', 'ignore')),
status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'approved', 'executing', 'completed', 'failed', 'aborted')),
plan_output TEXT,
approved_by UUID REFERENCES users(id),
approved_at TIMESTAMPTZ,
completed_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX idx_remediation_tenant ON remediation_actions(tenant_id, stack_name);
-- Notification preferences
CREATE TABLE notification_configs (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
channel TEXT NOT NULL CHECK (channel IN ('slack', 'email', 'webhook', 'pagerduty')),
config JSONB NOT NULL DEFAULT '{}',
min_severity TEXT NOT NULL DEFAULT 'medium' CHECK (min_severity IN ('critical', 'high', 'medium', 'low')),
enabled BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE(tenant_id, channel)
);
-- Feature flags
CREATE TABLE feature_flags (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
tenant_id UUID REFERENCES tenants(id) ON DELETE CASCADE,
flag_key TEXT NOT NULL,
enabled BOOLEAN NOT NULL DEFAULT false,
rollout_pct INT NOT NULL DEFAULT 0 CHECK (rollout_pct BETWEEN 0 AND 100),
metadata JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE(tenant_id, flag_key)
);
-- Row Level Security
ALTER TABLE drift_reports ENABLE ROW LEVEL SECURITY;
ALTER TABLE remediation_actions ENABLE ROW LEVEL SECURITY;
ALTER TABLE notification_configs ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation_drift ON drift_reports
USING (tenant_id::text = current_setting('app.tenant_id', true));
CREATE POLICY tenant_isolation_remediation ON remediation_actions
USING (tenant_id::text = current_setting('app.tenant_id', true));
CREATE POLICY tenant_isolation_notifications ON notification_configs
USING (tenant_id::text = current_setting('app.tenant_id', true));