115 lines
4.5 KiB
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);
|