Files
git.stella-ops.org/devops/compose/postgres-init/05-policy-exceptions-schema.sql

115 lines
4.5 KiB
SQL

-- Policy exceptions schema bootstrap for compose environments.
-- Ensures exception endpoints can start against a clean database.
CREATE EXTENSION IF NOT EXISTS pgcrypto;
CREATE SCHEMA IF NOT EXISTS policy;
CREATE TABLE IF NOT EXISTS policy.recheck_policies (
policy_id TEXT PRIMARY KEY,
tenant_id TEXT NOT NULL,
name TEXT NOT NULL,
conditions JSONB NOT NULL,
default_action TEXT NOT NULL,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_recheck_policies_tenant
ON policy.recheck_policies (tenant_id, is_active);
CREATE TABLE IF NOT EXISTS policy.exceptions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id TEXT NOT NULL,
name TEXT NOT NULL,
description TEXT,
rule_pattern TEXT,
resource_pattern TEXT,
artifact_pattern TEXT,
project_id TEXT,
reason TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('proposed', 'approved', 'active', 'expired', 'revoked')),
expires_at TIMESTAMPTZ,
approved_by TEXT,
approved_at TIMESTAMPTZ,
revoked_by TEXT,
revoked_at TIMESTAMPTZ,
metadata JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by TEXT,
exception_id TEXT NOT NULL UNIQUE,
version INTEGER NOT NULL DEFAULT 1,
type TEXT NOT NULL DEFAULT 'policy' CHECK (type IN ('vulnerability', 'policy', 'unknown', 'component')),
artifact_digest TEXT,
purl_pattern TEXT,
vulnerability_id TEXT,
policy_rule_id TEXT,
environments TEXT[] NOT NULL DEFAULT '{}',
owner_id TEXT,
requester_id TEXT,
approver_ids TEXT[] NOT NULL DEFAULT '{}',
reason_code TEXT DEFAULT 'other' CHECK (reason_code IN (
'false_positive',
'accepted_risk',
'compensating_control',
'test_only',
'vendor_not_affected',
'scheduled_fix',
'deprecation_in_progress',
'runtime_mitigation',
'network_isolation',
'other'
)),
rationale TEXT,
evidence_refs JSONB NOT NULL DEFAULT '[]',
compensating_controls JSONB NOT NULL DEFAULT '[]',
ticket_ref TEXT,
recheck_policy_id TEXT REFERENCES policy.recheck_policies(policy_id),
last_recheck_result JSONB,
last_recheck_at TIMESTAMPTZ,
UNIQUE (tenant_id, name)
);
CREATE INDEX IF NOT EXISTS idx_exceptions_tenant ON policy.exceptions(tenant_id);
CREATE INDEX IF NOT EXISTS idx_exceptions_status ON policy.exceptions(tenant_id, status);
CREATE INDEX IF NOT EXISTS idx_exceptions_expires ON policy.exceptions(expires_at) WHERE status = 'active';
CREATE INDEX IF NOT EXISTS idx_exceptions_project ON policy.exceptions(tenant_id, project_id);
CREATE INDEX IF NOT EXISTS idx_exceptions_vuln_id ON policy.exceptions(vulnerability_id) WHERE vulnerability_id IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_exceptions_purl ON policy.exceptions(purl_pattern) WHERE purl_pattern IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_exceptions_artifact ON policy.exceptions(artifact_digest) WHERE artifact_digest IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_exceptions_policy_rule ON policy.exceptions(policy_rule_id) WHERE policy_rule_id IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_exceptions_owner ON policy.exceptions(owner_id) WHERE owner_id IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_exceptions_recheck_policy ON policy.exceptions(tenant_id, recheck_policy_id) WHERE recheck_policy_id IS NOT NULL;
CREATE TABLE IF NOT EXISTS policy.exception_events (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
exception_id TEXT NOT NULL REFERENCES policy.exceptions(exception_id) ON DELETE CASCADE,
sequence_number INTEGER NOT NULL,
event_type TEXT NOT NULL CHECK (event_type IN (
'created',
'updated',
'approved',
'activated',
'extended',
'revoked',
'expired',
'evidence_attached',
'compensating_control_added',
'rejected'
)),
actor_id TEXT NOT NULL,
occurred_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
previous_status TEXT,
new_status TEXT NOT NULL,
new_version INTEGER NOT NULL,
description TEXT,
details JSONB NOT NULL DEFAULT '{}',
client_info TEXT,
UNIQUE (exception_id, sequence_number)
);
CREATE INDEX IF NOT EXISTS idx_exception_events_exception ON policy.exception_events(exception_id);
CREATE INDEX IF NOT EXISTS idx_exception_events_time ON policy.exception_events USING BRIN (occurred_at);