บทนำ
ในระบบ microservices สมัยใหม่ API Gateway ทำหน้าที่เป็นจุดเชื่อมต่อกลางที่รับ request ทั้งหมดเข้ามา การวิเคราะห์ log จาก API Gateway อย่างมีประสิทธิภาพจึงเป็นสิ่งจำเป็นอย่างยิ่งสำหรับการรักษาความปลอดภัยและประสิทธิภาพการทำงาน บทความนี้จะอธิบายเทคนิคขั้นสูงในการวิเคราะห์ log เพื่อระบุ abnormal traffic พร้อมโค้ด production-ready ที่สามารถนำไปใช้งานได้จริง
สถาปัตยกรรมระบบ Log Analysis
ระบบ API Gateway Log Analysis ที่ดีควรประกอบด้วยองค์ประกอบหลักดังนี้ ส่วนแรกคือ Log Collector ที่ทำหน้าที่รวบรวม log จาก API Gateway หลายตัว ส่วนที่สองคือ Log Parser ที่ parse ข้อมูลดิบให้เป็น structured data ส่วนที่สามคือ Anomaly Detector ที่วิเคราะห์ patterns เพื่อระบุความผิดปกติ และส่วนสุดท้ายคือ Alert Manager ที่จัดการส่งการแจ้งเตือนเมื่อพบสิ่งผิดปกติ
การออกแบบ Log Schema
ก่อนที่จะเริ่มวิเคราะห์ เราต้องกำหนดโครงสร้างของ log ให้เป็นมาตรฐานเดียวกัน โดย log แต่ละรายการควรประกอบด้วยฟิลด์ที่จำเป็นสำหรับการวิเคราะห์ ซึ่งรวมถึง timestamp ที่บันทึกเวลาละเอียดถึงมิลลิวินาที, client_ip ที่ระบุที่อยู่ IP ของผู้ใช้งาน, request_path ที่เก็บ path ของ API ที่ถูกเรียก, http_method ที่ระบุ GET POST PUT DELETE และอื่นๆ, status_code ที่เก็บ HTTP status ของ response, response_time_ms ที่วัดเวลาตอบสนองเป็นมิลิวินาที, request_size_bytes ที่วัดขนาดของ request, user_agent ที่เก็บข้อมูล browser หรือ client, และ trace_id สำหรับการ track request ผ่านระบบ
import json
import re
from dataclasses import dataclass
from datetime import datetime
from typing import Optional
@dataclass
class APILogEntry:
"""Structured log entry สำหรับ API Gateway"""
timestamp: datetime
client_ip: str
request_path: str
http_method: str
status_code: int
response_time_ms: float
request_size_bytes: int
user_agent: str
trace_id: str
service_name: str
api_key_id: Optional[str] = None
error_message: Optional[str] = None
class APILogParser:
"""Parser สำหรับ API Gateway log ในรูปแบบ JSON"""
# Pattern สำหรับ Apache/Nginx combined log format
LOG_PATTERN = re.compile(
r'(?P<ip>[\d\.]+) - (?P<user>[\w\-]+) '
r'\[(?P<timestamp>[^\]]+)\] '
r'"(?P<method>\w+) (?P<path>[^\s]+)[^"]*" '
r'(?P<status>\d+) (?P<size>\d+) '
r'"(?P<referer>[^"]*)" "(?P<ua>[^"]*)"'
)
# JSON field mapping
JSON_FIELDS = {
'timestamp': ['timestamp', 'time', '@timestamp', 'ts'],
'client_ip': ['client_ip', 'remote_addr', 'ip', 'clientIp'],
'request_path': ['request_path', 'path', 'uri', 'requestUri'],
'http_method': ['http_method', 'method', 'verb', 'httpMethod'],
'status_code': ['status_code', 'status', 'statusCode'],
'response_time_ms': ['response_time_ms', 'response_time', 'latency', 'duration'],
'request_size_bytes': ['request_size_bytes', 'request_size', 'size'],
'user_agent': ['user_agent', 'user_agent', 'ua'],
'trace_id': ['trace_id', 'traceId', 'request_id', 'requestId'],
'service_name': ['service_name', 'service', 'app'],
}
def parse_json_line(self, line: str) -> Optional[APILogEntry]:
"""Parse log line ในรูปแบบ JSON"""
try:
data = json.loads(line)
return self._extract_fields(data)
except json.JSONDecodeError:
return None
def _extract_fields(self, data: dict) -> APILogEntry:
"""Extract fields จาก dict โดยใช้ field mapping"""
def find_value(key_list):
for key in key_list:
if key in data:
return data[key]
return None
timestamp_str = find_value(self.JSON_FIELDS['timestamp'])
if isinstance(timestamp_str, str):
timestamp = datetime.fromisoformat(timestamp_str.replace('Z', '+00:00'))
else:
timestamp = datetime.now()
return APILogEntry(
timestamp=timestamp,
client_ip=str(find_value(self.JSON_FIELDS['client_ip']) or '0.0.0.0'),
request_path=str(find_value(self.JSON_FIELDS['request_path']) or '/'),
http_method=str(find_value(self.JSON_FIELDS['http_method']) or 'GET'),
status_code=int(find_value(self.JSON_FIELDS['status_code']) or 0),
response_time_ms=float(find_value(self.JSON_FIELDS['response_time_ms']) or 0),
request_size_bytes=int(find_value(self.JSON_FIELDS['request_size_bytes']) or 0),
user_agent=str(find_value(self.JSON_FIELDS['user_agent']) or ''),
trace_id=str(find_value(self.JSON_FIELDS['trace_id']) or ''),
service_name=str(find_value(self.JSON_FIELDS['service_name']) or 'unknown'),
)
ตัวอย่างการใช้งาน
sample_log = json.dumps({
"timestamp": "2024-01-15T10:30:45.123Z",
"client_ip": "192.168.1.100",
"request_path": "/api/v1/users",
"method": "GET",
"status": 200,
"response_time": 45.5,
"traceId": "abc123xyz"
})
parser = APILogParser()
entry = parser.parse_json_line(sample_log)
print(f"Parsed: {entry.request_path} from {entry.client_ip} in {entry.response_time_ms}ms")
อัลกอริทึมการตรวจจับ Anomaly
การตรวจจับการรับส่งข้อมูลผิดปกติต้องใช้หลายเทคนิคร่วมกัน โดยแต่ละเทคนิคจะจับ pattern ที่แตกต่างกัน เทคนิคแรกคือ Rate-based Detection ที่ตรวจจับ request rate ที่ผิดปกติ เช่น brute force attack หรือ scraping เทคนิคที่สองคือ Statistical Anomaly Detection ที่ใช้ statistical methods เช่น Z-score หรือ IQR เพื่อหา outliers ใน response time หรือ request size เทคนิคที่สามคือ Pattern-based Detection ที่ตรวจจับ known attack patterns เช่น SQL injection, path traversal หรือ XSS และเทคนิคสุดท้ายคือ ML-based Detection ที่ใช้ machine learning เพื่อจำแนก traffic ที่น่าสงสัย
from collections import defaultdict, deque
from dataclasses import dataclass
from enum import Enum
from typing import Dict, List, Tuple
import math
class ThreatLevel(Enum):
LOW = "low"
MEDIUM = "medium"
HIGH = "high"
CRITICAL = "critical"
@dataclass
class AnomalyResult:
threat_level: ThreatLevel
anomaly_type: str
description: str
evidence: dict
recommended_action: str
class AnomalyDetector:
"""ระบบตรวจจับ anomaly แบบ multi-layer"""
def __init__(
self,
rate_threshold: int = 100, # requests per minute
error_rate_threshold: float = 0.1, # 10%
p99_latency_threshold_ms: float = 500,
sliding_window_size: int = 60 # seconds
):
self.rate_threshold = rate_threshold
self.error_rate_threshold = error_rate_threshold
self.p99_latency_threshold = p99_latency_threshold_ms
# In-memory storage สำหรับ real-time analysis
self.ip_request_times: Dict[str, deque] = defaultdict(lambda: deque(maxlen=1000))
self.ip_errors: Dict[str, deque] = defaultdict(lambda: deque(maxlen=100))
self.ip_latencies: Dict[str, deque] = defaultdict(lambda: deque(maxlen=500))
self.endpoint_stats: Dict[str, dict] = defaultdict(lambda: {
'total': 0, 'errors': 0, 'latencies': deque(maxlen=1000)
})
# Known attack patterns
self.attack_patterns = [
(r'(\bunion\b.*\bselect\b|\bor\b1\s*=\s*1)', 'SQL_INJECTION'),
(r'(\.\./|\.\.\\|%2e%2e)', 'PATH_TRAVERSAL'),
(r'(