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:
115
products/02-iac-drift-detection/saas/migrations/001_init.sql
Normal file
115
products/02-iac-drift-detection/saas/migrations/001_init.sql
Normal 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));
|
||||
Reference in New Issue
Block a user