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>
143 lines
3.9 KiB
Bash
143 lines
3.9 KiB
Bash
#!/bin/sh
|
|
# SecuBox AI Gateway - Audit Logging
|
|
# ANSSI CSPN compliance audit trail
|
|
|
|
CONFIG="ai-gateway"
|
|
|
|
# Get audit configuration
|
|
get_audit_config() {
|
|
AUDIT_ENABLED=$(uci -q get ${CONFIG}.audit.enabled || echo "1")
|
|
AUDIT_PATH=$(uci -q get ${CONFIG}.audit.audit_path || echo "/var/log/ai-gateway/audit.jsonl")
|
|
LOG_REQUESTS=$(uci -q get ${CONFIG}.audit.log_requests || echo "1")
|
|
LOG_RESPONSES=$(uci -q get ${CONFIG}.audit.log_responses || echo "0")
|
|
LOG_CLASSIFICATIONS=$(uci -q get ${CONFIG}.audit.log_classifications || echo "1")
|
|
RETENTION_DAYS=$(uci -q get ${CONFIG}.audit.retention_days || echo "90")
|
|
MAX_LOG_SIZE_MB=$(uci -q get ${CONFIG}.audit.max_log_size_mb || echo "100")
|
|
}
|
|
|
|
# Initialize audit logging
|
|
init_audit() {
|
|
get_audit_config
|
|
mkdir -p "$(dirname "$AUDIT_PATH")"
|
|
}
|
|
|
|
# Log a request
|
|
# Args: $1=request_id, $2=classification, $3=provider, $4=model, $5=sanitized (0/1)
|
|
audit_log_request() {
|
|
get_audit_config
|
|
[ "$AUDIT_ENABLED" != "1" ] && return
|
|
[ "$LOG_REQUESTS" != "1" ] && return
|
|
|
|
local request_id="$1"
|
|
local classification="$2"
|
|
local provider="$3"
|
|
local model="$4"
|
|
local sanitized="$5"
|
|
local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
|
|
printf '{"timestamp":"%s","request_id":"%s","type":"request","classification":"%s","provider":"%s","model":"%s","sanitized":%s}\n' \
|
|
"$timestamp" \
|
|
"$request_id" \
|
|
"$classification" \
|
|
"$provider" \
|
|
"$model" \
|
|
"$([ "$sanitized" = "1" ] && echo "true" || echo "false")" \
|
|
>> "$AUDIT_PATH"
|
|
}
|
|
|
|
# Log a response
|
|
# Args: $1=request_id, $2=status, $3=latency_ms
|
|
audit_log_response() {
|
|
get_audit_config
|
|
[ "$AUDIT_ENABLED" != "1" ] && return
|
|
[ "$LOG_RESPONSES" != "1" ] && return
|
|
|
|
local request_id="$1"
|
|
local status="$2"
|
|
local latency_ms="$3"
|
|
local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
|
|
printf '{"timestamp":"%s","request_id":"%s","type":"response","status":"%s","latency_ms":%s}\n' \
|
|
"$timestamp" \
|
|
"$request_id" \
|
|
"$status" \
|
|
"$latency_ms" \
|
|
>> "$AUDIT_PATH"
|
|
}
|
|
|
|
# Log a classification decision
|
|
# Args: $1=request_id, $2=classification, $3=reason, $4=pattern
|
|
audit_log_classification() {
|
|
get_audit_config
|
|
[ "$AUDIT_ENABLED" != "1" ] && return
|
|
[ "$LOG_CLASSIFICATIONS" != "1" ] && return
|
|
|
|
local request_id="$1"
|
|
local classification="$2"
|
|
local reason="$3"
|
|
local pattern="$4"
|
|
local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
|
|
printf '{"timestamp":"%s","request_id":"%s","type":"classification","classification":"%s","reason":"%s","pattern":"%s"}\n' \
|
|
"$timestamp" \
|
|
"$request_id" \
|
|
"$classification" \
|
|
"$reason" \
|
|
"$pattern" \
|
|
>> "$AUDIT_PATH"
|
|
}
|
|
|
|
# Get audit statistics
|
|
get_audit_stats() {
|
|
get_audit_config
|
|
|
|
if [ ! -f "$AUDIT_PATH" ]; then
|
|
printf '{"total":0,"local_only":0,"sanitized":0,"cloud_direct":0}'
|
|
return
|
|
fi
|
|
|
|
local total=$(wc -l < "$AUDIT_PATH" 2>/dev/null || echo 0)
|
|
local local_only=$(grep -c '"local_only"' "$AUDIT_PATH" 2>/dev/null || echo 0)
|
|
local sanitized=$(grep -c '"sanitized"' "$AUDIT_PATH" 2>/dev/null || echo 0)
|
|
local cloud_direct=$(grep -c '"cloud_direct"' "$AUDIT_PATH" 2>/dev/null || echo 0)
|
|
|
|
printf '{"total":%d,"local_only":%d,"sanitized":%d,"cloud_direct":%d}' \
|
|
"$total" "$local_only" "$sanitized" "$cloud_direct"
|
|
}
|
|
|
|
# Rotate audit logs
|
|
rotate_audit_logs() {
|
|
get_audit_config
|
|
|
|
[ ! -f "$AUDIT_PATH" ] && return
|
|
|
|
# Check file size
|
|
local size_kb=$(du -k "$AUDIT_PATH" 2>/dev/null | cut -f1)
|
|
local max_size_kb=$((MAX_LOG_SIZE_MB * 1024))
|
|
|
|
if [ "$size_kb" -gt "$max_size_kb" ]; then
|
|
local backup="${AUDIT_PATH}.$(date +%Y%m%d-%H%M%S)"
|
|
mv "$AUDIT_PATH" "$backup"
|
|
gzip "$backup" 2>/dev/null
|
|
logger -t ai-gateway "Rotated audit log: $backup.gz"
|
|
fi
|
|
|
|
# Clean old logs
|
|
find "$(dirname "$AUDIT_PATH")" -name "*.gz" -mtime +${RETENTION_DAYS} -delete 2>/dev/null
|
|
}
|
|
|
|
# Export audit log in ANSSI format
|
|
export_audit_anssi() {
|
|
get_audit_config
|
|
|
|
local export_file="/tmp/ai-gateway-audit-$(date +%Y%m%d-%H%M%S).jsonl"
|
|
|
|
if [ -f "$AUDIT_PATH" ]; then
|
|
cp "$AUDIT_PATH" "$export_file"
|
|
gzip "$export_file"
|
|
echo "${export_file}.gz"
|
|
else
|
|
echo ""
|
|
fi
|
|
}
|