Implement secubox-ai-gateway package with intelligent AI request routing based on data sensitivity classification for GDPR/ANSSI compliance. Features: - 3-tier data classification: LOCAL_ONLY, SANITIZED, CLOUD_DIRECT - Provider hierarchy: LocalAI > Mistral (EU) > Claude > GPT > Gemini > xAI - PII sanitizer: IPv4/IPv6, MAC, credentials, private keys scrubbing - OpenAI-compatible API proxy on port 4050 - aigatewayctl CLI: status, classify, sanitize, provider, audit commands - RPCD backend with 11 ubus methods for LuCI integration - ANSSI CSPN audit logging in JSONL format Classification patterns detect: - IP addresses, MAC addresses, private keys - Credentials (password, secret, token, api_key) - System paths, security tool references - WireGuard configuration data All cloud providers are opt-in. Default LOCAL_ONLY ensures data sovereignty - sensitive data never leaves the device. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
137 lines
3.8 KiB
Bash
137 lines
3.8 KiB
Bash
#!/bin/sh
|
|
# SecuBox AI Gateway - Provider Routing Logic
|
|
# Selects appropriate AI provider based on data classification
|
|
|
|
CONFIG="ai-gateway"
|
|
PROVIDERS_DIR="/usr/lib/ai-gateway/providers"
|
|
|
|
# Load all provider configurations
|
|
load_providers() {
|
|
. /lib/functions.sh
|
|
config_load "$CONFIG"
|
|
}
|
|
|
|
# Get ordered list of enabled providers for a classification level
|
|
# Args: $1 = classification (local_only, sanitized, cloud_direct)
|
|
# Output: space-separated list of provider names sorted by priority
|
|
get_enabled_providers() {
|
|
local classification="$1"
|
|
local providers=""
|
|
|
|
# Iterate through all provider sections
|
|
for section in localai mistral claude openai gemini xai; do
|
|
local enabled=$(uci -q get ${CONFIG}.${section}.enabled)
|
|
[ "$enabled" != "1" ] && continue
|
|
|
|
local prov_class=$(uci -q get ${CONFIG}.${section}.classification)
|
|
local priority=$(uci -q get ${CONFIG}.${section}.priority || echo 99)
|
|
|
|
# Filter by classification level
|
|
case "$classification" in
|
|
local_only)
|
|
# Only LocalAI for local_only
|
|
[ "$prov_class" = "local_only" ] || continue
|
|
;;
|
|
sanitized)
|
|
# LocalAI or EU providers (Mistral)
|
|
[ "$prov_class" = "local_only" ] || [ "$prov_class" = "sanitized" ] || continue
|
|
;;
|
|
cloud_direct)
|
|
# Any provider allowed
|
|
;;
|
|
esac
|
|
|
|
providers="${providers}${priority}:${section} "
|
|
done
|
|
|
|
# Sort by priority and return provider names
|
|
echo "$providers" | tr ' ' '\n' | sort -t: -k1 -n | cut -d: -f2 | tr '\n' ' '
|
|
}
|
|
|
|
# Check if a provider is available (has API key or is local)
|
|
provider_available() {
|
|
local provider="$1"
|
|
|
|
case "$provider" in
|
|
localai)
|
|
# Check if LocalAI is running
|
|
local endpoint=$(uci -q get ${CONFIG}.localai.endpoint || echo "http://127.0.0.1:8081")
|
|
wget -q -O /dev/null --timeout=2 "${endpoint}/readyz" 2>/dev/null
|
|
return $?
|
|
;;
|
|
*)
|
|
# Cloud providers need API key
|
|
local api_key=$(uci -q get ${CONFIG}.${provider}.api_key)
|
|
[ -n "$api_key" ] && return 0
|
|
return 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# Select best available provider for a classification
|
|
# Args: $1 = classification
|
|
# Output: provider name or empty if none available
|
|
select_provider() {
|
|
local classification="$1"
|
|
|
|
for provider in $(get_enabled_providers "$classification"); do
|
|
[ -z "$provider" ] && continue
|
|
if provider_available "$provider"; then
|
|
echo "$provider"
|
|
return 0
|
|
fi
|
|
done
|
|
|
|
# No provider available
|
|
return 1
|
|
}
|
|
|
|
# Route request to selected provider
|
|
# Args: $1 = JSON request, $2 = classification
|
|
# Output: JSON response
|
|
route_request() {
|
|
local request_json="$1"
|
|
local classification="$2"
|
|
|
|
local provider=$(select_provider "$classification")
|
|
if [ -z "$provider" ]; then
|
|
printf '{"error":{"code":"no_provider","message":"No provider available for classification: %s"}}' "$classification"
|
|
return 1
|
|
fi
|
|
|
|
# Source provider adapter
|
|
local adapter="${PROVIDERS_DIR}/${provider}.sh"
|
|
if [ ! -f "$adapter" ]; then
|
|
printf '{"error":{"code":"adapter_missing","message":"Provider adapter not found: %s"}}' "$provider"
|
|
return 1
|
|
fi
|
|
|
|
. "$adapter"
|
|
|
|
# Call provider-specific handler
|
|
provider_request "$request_json"
|
|
}
|
|
|
|
# Get provider info for status display
|
|
get_provider_info() {
|
|
local provider="$1"
|
|
|
|
local enabled=$(uci -q get ${CONFIG}.${provider}.enabled || echo "0")
|
|
local priority=$(uci -q get ${CONFIG}.${provider}.priority || echo "-")
|
|
local classification=$(uci -q get ${CONFIG}.${provider}.classification || echo "-")
|
|
local model=$(uci -q get ${CONFIG}.${provider}.model || echo "-")
|
|
|
|
local status="unavailable"
|
|
if [ "$enabled" = "1" ]; then
|
|
provider_available "$provider" && status="available" || status="configured"
|
|
fi
|
|
|
|
printf '{"name":"%s","enabled":%s,"priority":%s,"classification":"%s","model":"%s","status":"%s"}' \
|
|
"$provider" \
|
|
"$([ "$enabled" = "1" ] && echo "true" || echo "false")" \
|
|
"$priority" \
|
|
"$classification" \
|
|
"$model" \
|
|
"$status"
|
|
}
|