CREATE TABLE IF NOT EXISTS vpn_connections ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), cluster_id UUID NOT NULL REFERENCES clusters(id) ON DELETE CASCADE, organization_id UUID NOT NULL REFERENCES organizations(id) ON DELETE CASCADE, name TEXT NOT NULL, target_endpoint JSONB NOT NULL DEFAULT '{}'::JSONB, protocol_family TEXT NOT NULL DEFAULT 'generic', credential_ref TEXT, mode TEXT NOT NULL DEFAULT 'single_active', desired_state TEXT NOT NULL DEFAULT 'disabled', allowed_node_policy JSONB NOT NULL DEFAULT '{"mode":"explicit","node_ids":[]}'::JSONB, routing_usage JSONB NOT NULL DEFAULT '[]'::JSONB, route_policy JSONB NOT NULL DEFAULT '{}'::JSONB, qos_policy JSONB NOT NULL DEFAULT '{}'::JSONB, placement_policy JSONB NOT NULL DEFAULT '{}'::JSONB, status TEXT NOT NULL DEFAULT 'disabled', metadata JSONB NOT NULL DEFAULT '{}'::JSONB, created_by_user_id UUID REFERENCES users(id) ON DELETE SET NULL, updated_by_user_id UUID REFERENCES users(id) ON DELETE SET NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), CONSTRAINT vpn_connections_mode_check CHECK (mode IN ('single_active')), CONSTRAINT vpn_connections_desired_state_check CHECK (desired_state IN ('enabled', 'disabled')), CONSTRAINT vpn_connections_status_check CHECK (status IN ('disabled', 'enabled', 'connecting', 'active', 'degraded', 'failed')), CONSTRAINT vpn_connections_protocol_family_check CHECK (protocol_family IN ('generic', 'wireguard', 'ipsec', 'openvpn')) ); CREATE UNIQUE INDEX IF NOT EXISTS idx_vpn_connections_cluster_org_name ON vpn_connections(cluster_id, organization_id, lower(name)); CREATE INDEX IF NOT EXISTS idx_vpn_connections_cluster_org_state ON vpn_connections(cluster_id, organization_id, desired_state, status); CREATE TABLE IF NOT EXISTS vpn_connection_allowed_nodes ( vpn_connection_id UUID NOT NULL REFERENCES vpn_connections(id) ON DELETE CASCADE, cluster_id UUID NOT NULL REFERENCES clusters(id) ON DELETE CASCADE, node_id UUID NOT NULL REFERENCES nodes(id) ON DELETE CASCADE, role_preference TEXT NOT NULL DEFAULT 'candidate', status TEXT NOT NULL DEFAULT 'active', metadata JSONB NOT NULL DEFAULT '{}'::JSONB, created_by_user_id UUID REFERENCES users(id) ON DELETE SET NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), PRIMARY KEY (vpn_connection_id, node_id), CONSTRAINT vpn_connection_allowed_nodes_membership_fk FOREIGN KEY (cluster_id, node_id) REFERENCES cluster_memberships(cluster_id, node_id) ON DELETE CASCADE, CONSTRAINT vpn_connection_allowed_nodes_preference_check CHECK (role_preference IN ('candidate', 'standby', 'preferred')), CONSTRAINT vpn_connection_allowed_nodes_status_check CHECK (status IN ('active', 'disabled')) ); CREATE INDEX IF NOT EXISTS idx_vpn_connection_allowed_nodes_cluster_node ON vpn_connection_allowed_nodes(cluster_id, node_id, status); CREATE TABLE IF NOT EXISTS vpn_connection_route_policies ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), vpn_connection_id UUID NOT NULL REFERENCES vpn_connections(id) ON DELETE CASCADE, cluster_id UUID NOT NULL REFERENCES clusters(id) ON DELETE CASCADE, organization_id UUID NOT NULL REFERENCES organizations(id) ON DELETE CASCADE, route_type TEXT NOT NULL, destination TEXT NOT NULL, action TEXT NOT NULL DEFAULT 'allow', service_type TEXT, priority INTEGER NOT NULL DEFAULT 100, policy JSONB NOT NULL DEFAULT '{}'::JSONB, status TEXT NOT NULL DEFAULT 'active', created_by_user_id UUID REFERENCES users(id) ON DELETE SET NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), CONSTRAINT vpn_connection_route_policies_route_type_check CHECK (route_type IN ('cidr', 'dns_suffix', 'service', 'resource')), CONSTRAINT vpn_connection_route_policies_action_check CHECK (action IN ('allow', 'deny')), CONSTRAINT vpn_connection_route_policies_status_check CHECK (status IN ('active', 'disabled')) ); CREATE INDEX IF NOT EXISTS idx_vpn_connection_route_policies_connection ON vpn_connection_route_policies(vpn_connection_id, status, priority); CREATE INDEX IF NOT EXISTS idx_vpn_connection_route_policies_cluster_org ON vpn_connection_route_policies(cluster_id, organization_id, route_type, status); CREATE TABLE IF NOT EXISTS vpn_connection_leases ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), vpn_connection_id UUID NOT NULL REFERENCES vpn_connections(id) ON DELETE CASCADE, cluster_id UUID NOT NULL REFERENCES clusters(id) ON DELETE CASCADE, owner_node_id UUID NOT NULL REFERENCES nodes(id) ON DELETE CASCADE, lease_generation BIGINT NOT NULL, fencing_token TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'active', acquired_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), renewed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), expires_at TIMESTAMPTZ NOT NULL, released_at TIMESTAMPTZ, fenced_at TIMESTAMPTZ, metadata JSONB NOT NULL DEFAULT '{}'::JSONB, CONSTRAINT vpn_connection_leases_membership_fk FOREIGN KEY (cluster_id, owner_node_id) REFERENCES cluster_memberships(cluster_id, node_id) ON DELETE CASCADE, CONSTRAINT vpn_connection_leases_status_check CHECK (status IN ('active', 'released', 'expired', 'fenced')), CONSTRAINT vpn_connection_leases_expiry_check CHECK (expires_at > acquired_at) ); CREATE UNIQUE INDEX IF NOT EXISTS idx_vpn_connection_leases_generation ON vpn_connection_leases(vpn_connection_id, lease_generation); CREATE UNIQUE INDEX IF NOT EXISTS idx_vpn_connection_leases_single_active ON vpn_connection_leases(vpn_connection_id) WHERE status = 'active'; CREATE INDEX IF NOT EXISTS idx_vpn_connection_leases_owner ON vpn_connection_leases(cluster_id, owner_node_id, status, expires_at); CREATE INDEX IF NOT EXISTS idx_vpn_connection_leases_connection_time ON vpn_connection_leases(vpn_connection_id, acquired_at DESC);