ในยุคที่ข้อมูลคือสินทรัพย์สำคัญ การจัดการ Log จาก API Gateway อย่างถูกต้องตามกฎหมายกลายเป็นสิ่งจำเป็นอย่างยิ่ง โดยเฉพาะกฎหมาย PDPA ของไทยที่กำหนดให้ข้อมูลส่วนบุคคลต้องได้รับการคุ้มครองตั้งแต่การเก็บรวบรวม การประมวลผล ไปจนถึงการจัดเก็บ บทความนี้จะแนะนำวิธีการตั้งค่า Log บน HolySheep AI API Gateway ให้ปฏิบัติตามกฎหมาย ใช้งานได้จริง พร้อมโค้ดตัวอย่างที่รันได้ทันที

ทำไมต้องทำ Log ให้เป็นความลับ (Log Anonymization)

เมื่อผู้ใช้งานส่งคำขอผ่าน API Gateway โดยปกติ Log จะเก็บข้อมูลต่อไปนี้

หากไม่ทำ Log ให้เป็นความลับ องค์กรอาจเผชิญความเสี่ยงด้านกฎหมาย โดนปรับตาม PDPA สูงสุด 5 ล้านบาท หรือถูกฟ้องร้องจากลูกค้าที่ข้อมูลรั่วไหล นอกจากนี้ยังเป็นการละเมิด GDPR หากมีลูกค้าจากสหภาพยุโรปอีกด้วย

การตั้งค่า HolySheep API Gateway สำหรับ Log ที่ปลอดภัย

ขั้นตอนที่ 1: สร้าง Middleware สำหรับ Anonymization

สร้าง Middleware ที่จะควบคุมการทำ Log โดยกรองข้อมูลที่เป็นความลับออกก่อนเก็บลง Log Storage

// anonymize-middleware.js
const crypto = require('crypto');

// รายการฟิลด์ที่ต้องซ่อน (PII Fields)
const SENSITIVE_FIELDS = [
  'email', 'phone', 'name', 'surname', 
  'password', 'credit_card', 'national_id',
  'address', 'ip_address', 'api_key', 'token',
  'authorization', 'session_id', 'cookie'
];

// ฟังก์ชัน Hash สำหรับแทนที่ข้อมูลจริง
function hashValue(value, salt = 'HOLYSHEEP_SALT') {
  if (typeof value !== 'string') {
    return value;
  }
  return crypto
    .createHash('sha256')
    .update(salt + value)
    .digest('hex')
    .substring(0, 16);
}

// ทำ Anonymize ข้อมูลใน Object
function anonymizeObject(obj, depth = 0) {
  if (depth > 10 || obj === null || typeof obj !== 'object') {
    return obj;
  }

  const result = Array.isArray(obj) ? [] : {};

  for (const key of Object.keys(obj)) {
    const lowerKey = key.toLowerCase();
    
    // ตรวจสอบว่าเป็นฟิลด์ที่ต้องซ่อนหรือไม่
    const isSensitive = SENSITIVE_FIELDS.some(
      field => lowerKey.includes(field)
    );

    if (isSensitive) {
      result[key] = [REDACTED:${hashValue(String(obj[key]))}];
    } else if (typeof obj[key] === 'object') {
      result[key] = anonymizeObject(obj[key], depth + 1);
    } else {
      result[key] = obj[key];
    }
  }

  return result;
}

// Mask IP Address
function maskIP(ip) {
  if (!ip || typeof ip !== 'string') return '[REDACTED_IP]';
  
  // IPv4: 192.168.1.100 -> 192.168.1.xxx
  if (ip.includes('.')) {
    const parts = ip.split('.');
    return ${parts[0]}.${parts[1]}.${parts[2]}.xxx;
  }
  
  // IPv6: ตัดส่วนหลัง
  if (ip.includes(':')) {
    return ip.split(':').slice(0, 4).join(':') + ':xxxx';
  }
  
  return '[REDACTED_IP]';
}

// Middleware สำหรับ Express
function holySheepAnonymizeMiddleware(req, res, next) {
  const startTime = Date.now();

  // เก็บ Log แบบ Anonymized
  const logEntry = {
    timestamp: new Date().toISOString(),
    request_id: req.headers['x-request-id'] || crypto.randomUUID(),
    method: req.method,
    path: req.path,
    ip: maskIP(req.ip || req.connection.remoteAddress),
    user_agent: req.headers['user-agent']?.substring(0, 100) || 'Unknown',
    status_code: null,
    latency_ms: null,
    request_body: anonymizeObject(req.body || {}),
    // ไม่เก็บ Headers ที่มีข้อมูลความลับ
    headers: {
      'content-type': req.headers['content-type'],
      'x-api-version': req.headers['x-api-version']
    }
  };

  // ตรวจจับ Response
  const originalSend = res.send;
  res.send = function(body) {
    logEntry.status_code = res.statusCode;
    logEntry.latency_ms = Date.now() - startTime;
    
    // Anonymize Response ด้วย
    try {
      const parsedBody = JSON.parse(body);
      logEntry.response_body = anonymizeObject(parsedBody);
    } catch {
      logEntry.response_body = '[BINARY_DATA]';
    }

    // ส่ง Log ไปเก็บที่ Storage
    sendToSecureStorage(logEntry);

    return originalSend.call(this, body);
  };

  next();
}

// ส่ง Log ไปเก็บแบบ Encrypted
async function sendToSecureStorage(logEntry) {
  // เข้ารหัสก่อนส่ง
  const encrypted = encryptLog(JSON.stringify(logEntry));
  
  // ใช้ HolySheep API สำหรับ Log Storage
  const response = await fetch('https://api.holysheep.ai/v1/logs', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': Bearer ${process.env.HOLYSHEEP_API_KEY},
      'X-Log-Schema-Version': '2024-01'
    },
    body: JSON.stringify({ encrypted_log: encrypted })
  });
  
  return response.json();
}

function encryptLog(data) {
  const key = crypto.scryptSync(
    process.env.LOG_ENCRYPTION_KEY, 
    'salt', 
    32
  );
  const iv = crypto.randomBytes(16);
  const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
  
  let encrypted = cipher.update(data, 'utf8', 'hex');
  encrypted += cipher.final('hex');
  const authTag = cipher.getAuthTag();
  
  return {
    iv: iv.toString('hex'),
    authTag: authTag.toString('hex'),
    data: encrypted
  };
}

module.exports = holySheepAnonymizeMiddleware;

ขั้นตอนที่ 2: ตั้งค่า API Gateway Configuration

# holy-sheep-gateway-config.yaml
gateway:
  name: holy-sheep-production-gateway
  version: "2.0"
  region: southeast-asia
  
logging:
  level: info
  format: json
  storage:
    type: encrypted_s3
    bucket: holy-sheep-logs-prod
    prefix: anonymized-logs/
    retention_days: 90
    encryption: aes-256-gcm
    
anonymization:
  enabled: true
  rules:
    - field_pattern: "*.email"
      action: hash_sha256
      preserve_format: true
    - field_pattern: "*.phone"
      action: mask_last_4
    - field_pattern: "*.national_id"
      action: redact
    - field_pattern: "*.credit_card"
      action: redact
    - field_pattern: "*.password"
      action: redact
    - field_pattern: "headers.authorization"
      action: redact
    - field_pattern: "ip_address"
      action: anonymize_subnet

compliance:
  pdpa:
    enabled: true
    consent_required: true
    data minimization: true
    retention_policy: 90_days
  gdpr:
    enabled: true
    right_to_erasure: true
    data_portability: true
    
monitoring:
  pii_detector:
    enabled: true
    alert_on_match: true
    notification_webhook: "https://your-webhook.com/alert"
  audit_trail:
    enabled: true
    immutable: true
    blockchain_hash: optional

ขั้นตอนที่ 3: ใช้งานกับ Express.js

// server.js
const express = require('express');
const holySheepAnonymize = require('./anonymize-middleware');

const app = express();

// ใช้ Middleware สำหรับทำ Log ให้เป็นความลับ
app.use(holySheepAnonymizeMiddleware);

// ตัวอย่าง API Endpoint
app.post('/api/v1/submit-form', async (req, res) => {
  // req.body จะถูก Anonymize ก่อนเก็บ Log อัตโนมัติ
  // {
  //   email: "a1b2c3d4e5f6g7h8...", // Hash แล้ว
  //   phone: "xxx-xxx-9876",       // Mask แล้ว
  //   name: "[REDACTED:hashed]",   // Redact แล้ว
  //   message: "ข้อความที่ไม่มี PII" // เก็บปกติ
  // }
  
  const response = await processUserData(req.body);
  
  res.json({
    success: true,
    message: 'ข้อมูลถูกบันทึกอย่างปลอดภัย',
    request_id: req.headers['x-request-id']
  });
});

// ตัวอย่าง API Endpoint สำหรับ AI Chat
app.post('/api/v1/chat', async (req, res) => {
  const { model = 'gpt-4', messages } = req.body;
  
  // เรียกใช้ HolySheep AI API
  const response = await fetch('https://api.holysheep.ai/v1/chat/completions', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': Bearer ${process.env.HOLYSHEEP_API_KEY}
    },
    body: JSON.stringify({
      model: model,
      messages: messages,
      temperature: 0.7
    })
  });
  
  const data = await response.json();
  // Log Response ก็จะถูก Anonymize อัตโนมัติ
  
  res.json(data);
});

app.listen(3000, () => {
  console.log('🚀 HolySheep Gateway เริ่มทำงานพร้อม Log Anonymization');
});

รายละเอียดเทคนิคการทำ Log ให้เป็นความลับ

1. การ Hash แบบ One-Way สำหรับ Email และ Phone

สำหรับข้อมูลที่ต้องการเก็บเพื่อวิเคราะห์แต่ไม่ต้องการเห็นค่าจริง ใช้ Hash Function แบบ Salted SHA-256 ทำให้สามารถ Track ผู้ใช้เดิมได้โดยไม่รู้ค่าจริง

# log_anonymizer.py
import hashlib
import re
from typing import Any, Dict, List
from datetime import datetime

class LogAnonymizer:
    def __init__(self, salt: str = "HOLYSHEEP_SALT_V1"):
        self.salt = salt
        self.sensitive_patterns = {
            'email': re.compile(r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}'),
            'phone_th': re.compile(r'(\+66|0)[0-9]{8,9}'),
            'phone_intl': re.compile(r'\+?[0-9]{1,3}[-. ]?[0-9]{3}[-. ]?[0-9]{3}[-. ]?[0-9]{4}'),
            'national_id': re.compile(r'[0-9]{13}'),
            'credit_card': re.compile(r'[0-9]{4}[- ]?[0-9]{4}[- ]?[0-9]{4}[- ]?[0-9]{4}'),
            'ip_v4': re.compile(r'\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b'),
            'ip_v6': re.compile(r'[0-9a-fA-F:]+'),
        }
    
    def hash_value(self, value: str, prefix: str = "") -> str:
        """Hash ค่าด้วย Salt แบบ One-Way"""
        if not isinstance(value, str):
            value = str(value)
        hash_obj = hashlib.sha256()
        hash_obj.update(f"{self.salt}{value}".encode('utf-8'))
        short_hash = hash_obj.hexdigest()[:16]
        return f"[{prefix}{short_hash}]" if prefix else f"[HASH:{short_hash}]"
    
    def mask_phone(self, phone: str) -> str:
        """Mask เบอร์โทรศัพท์ เก็บ 4 ตัวท้าย"""
        digits = re.sub(r'[^\d]', '', phone)
        if len(digits) >= 4:
            return f"xxx-xxx-{digits[-4:]}"
        return "[REDACTED_PHONE]"
    
    def mask_ip(self, ip: str) -> str:
        """Mask IP เก็บ Subnet"""
        if not ip:
            return "[REDACTED_IP]"
        
        # IPv4: 192.168.1.100 -> 192.168.1.xxx
        if '.' in ip and not ':' in ip:
            parts = ip.split('.')
            if len(parts) == 4:
                return f"{parts[0]}.{parts[1]}.{parts[2]}.xxx"
        
        # IPv6: เก็บ 4 ส่วนแรก
        if ':' in ip:
            parts = ip.split(':')
            return ':'.join(parts[:4]) + ':xxxx'
        
        return "[REDACTED_IP]"
    
    def anonymize_string(self, text: str) -> str:
        """Anonymize ข้อความทั้งหมดใน String"""
        if not isinstance(text, str):
            return text
        
        result = text
        
        # Mask Email
        result = self.sensitive_patterns['email'].sub(
            lambda m: self.hash_value(m.group(), 'EMAIL:'),
            result
        )
        
        # Mask Phone Thailand
        result = self.sensitive_patterns['phone_th'].sub(
            lambda m: self.mask_phone(m.group()),
            result
        )
        
        # Mask Phone International
        result = self.sensitive_patterns['phone_intl'].sub(
            lambda m: self.mask_phone(m.group()),
            result
        )
        
        # Mask National ID (รหัสบัตรประชาชน)
        result = self.sensitive_patterns['national_id'].sub(
            lambda m: f"XXX-XX-{m.group()[-4:]}",
            result
        )
        
        # Mask Credit Card
        result = self.sensitive_patterns['credit_card'].sub(
            lambda m: f"XXXX-XXXX-XXXX-{m.group()[-4:]}",
            result
        )
        
        # Mask IP Address
        result = self.sensitive_patterns['ip_v4'].sub(
            lambda m: self.mask_ip(m.group()),
            result
        )
        
        return result
    
    def anonymize_dict(self, data: Dict[str, Any], path: str = "") -> Dict[str, Any]:
        """Anonymize Dictionary ทั้งหมด"""
        if not isinstance(data, dict):
            return data
        
        result = {}
        sensitive_keys = {
            'email', 'phone', 'name', 'surname', 'first_name', 'last_name',
            'password', 'passwd', 'secret', 'token', 'api_key', 'apikey',
            'credit_card', 'card_number', 'national_id', 'id_card',
            'address', 'street', 'ip', 'ip_address', 'client_ip',
            'authorization', 'auth', 'bearer', 'session', 'session_id',
            'cookie', 'user_id', 'userid', 'customer_id'
        }
        
        for key, value in data.items():
            lower_key = key.lower().replace('-', '_').replace(' ', '_')
            
            if lower_key in sensitive_keys:
                # Hash ค่าที่สำคัญ
                if lower_key in {'email', 'user_id', 'userid', 'customer_id'}:
                    result[key] = self.hash_value(str(value), f"{lower_key.upper()}:")
                elif lower_key in {'phone'}:
                    result[key] = self.mask_phone(str(value))
                elif lower_key in {'ip', 'ip_address', 'client_ip'}:
                    result[key] = self.mask_ip(str(value))
                else:
                    result[key] = "[REDACTED]"
            elif isinstance(value, dict):
                result[key] = self.anonymize_dict(value, f"{path}/{key}")
            elif isinstance(value, list):
                result[key] = [self.anonymize_dict(item, f"{path}/{key}") 
                              if isinstance(item, dict) else item 
                              for item in value]
            elif isinstance(value, str):
                result[key] = self.anonymize_string(value)
            else:
                result[key] = value
        
        return result

ทดสอบการทำงาน

if __name__ == "__main__": anonymizer = LogAnonymizer() test_data = { "user_id": "user_12345", "email": "[email protected]", "phone": "081-234-5678", "name": "สมหมาย", "surname": "ไพรสินธ์", "national_id": "1234567890123", "ip_address": "192.168.1.100", "order_details": { "product": "AI Credits", "price": 999.00 } } result = anonymizer.anonymize_dict(test_data) print("ผลลัพธ์การ Anonymize:") print(result)

การจัดเก็บ Log อย่างปลอดภัยตามมาตรฐาน PDPA

นอกจากการทำ Log ให้เป็นความลับแล้ว ยังต้องจัดเก็บอย่างถูกต้องตามกฎหมาย

หลักการจัดเก็บตาม PDPA

// secure-log-storage.js
const { S3Client, PutObjectCommand, GetObjectCommand } = require('@aws-sdk/client-s3');
const { KMSClient, EncryptCommand, DecryptCommand } = require('@aws-sdk/client-kms');
const { DynamoDBClient } = require('@aws-sdk/client-dynamodb');
const { DynamoDBDocumentClient, PutCommand, QueryCommand } = require('@aws-sdk/lib-dynamodb');
const crypto = require('crypto');

class SecureLogStorage {
  constructor(config) {
    this.s3 = new S3Client({ region: config.region });
    this.kms = new KMSClient({ region: config.region });
    this.dynamo = DynamoDBDocumentClient.from(new DynamoDBClient({ region: config.region }));
    
    this.bucketName = config.s3Bucket;
    this.tableName = config.dynamoTable;
    this.kmsKeyId = config.kmsKeyId;
    this.retentionDays = config.retentionDays || 90;
  }

  // เข้ารหัส Log ก่อนจัดเก็บ
  async encryptLog(logData) {
    const plaintext = JSON.stringify(logData);
    
    // Generate Data Key
    const { plaintext: dataKey } = await this.kms.send(
      new DecryptCommand({
        CiphertextBlob: Buffer.from(process.env.DEK_BASE64, 'base64')
      })
    );
    
    // Encrypt ด้วย AES-256-GCM
    const iv = crypto.randomBytes(16);
    const cipher = crypto.createCipheriv('aes-256-gcm', dataKey, iv);
    
    let encrypted = cipher.update(plaintext, 'utf8');
    encrypted = Buffer.concat([encrypted, cipher.final()]);
    const authTag = cipher.getAuthTag();
    
    return {
      iv: iv.toString('base64'),
      authTag: authTag.toString('base64'),
      encryptedData: encrypted.toString('base64'),
      keyId: this.kmsKeyId
    };
  }

  // จัดเก็บ Log แบบ Anonymized
  async storeLog(logEntry) {
    const timestamp = new Date();
    const logId = crypto.randomUUID();
    
    // เพิ่ม Metadata สำหรับ Compliance
    const compliantLog = {
      ...logEntry,
      _meta: {
        log_id: logId,
        stored_at: timestamp.toISOString(),
        retention_until: new Date(
          timestamp.getTime() + this.retentionDays * 24 * 60 * 60 * 1000
        ).toISOString(),
        encryption_version: 'AES-256-GCM-v1',
        anonymized: true,
        pii_detected: logEntry._meta?.pii_found || false
      }
    };

    // เข้ารหัสก่อนจัดเก็บ
    const encrypted = await this.encryptLog(compliantLog);
    
    // คำนวณ Checksum
    const checksum = crypto
      .createHash('sha256')
      .update(JSON.stringify(logEntry))
      .digest('hex');

    // จัดเก็บลง S3
    const s3Key = logs/${timestamp.getFullYear()}/${String(timestamp.getMonth() + 1).padStart(2, '0')}/${String(timestamp.getDate()).padStart(2, '0')}/${logId}.enc;
    
    await this.s3.send(new PutObjectCommand({
      Bucket: this.bucketName,
      Key: s3Key,
      Body: JSON.stringify(encrypted),
      ServerSideEncryption: 'aws:kms',
      SSEKMSKeyId: this.kmsKeyId,
      Metadata: {
        'checksum': checksum,
        'retention-days': String(this.retentionDays),
        'stored-at': timestamp.toISOString()
      }
    }));

    // จัดเก็บ Index ลง DynamoDB สำหรับ Query
    await this.dynamo.send(new PutCommand({
      TableName: this.tableName,
      Item: {
        log_id: logId,
        timestamp: timestamp.toISOString(),
        retention_until: compliantLog._meta.retention_until,
        request_id: logEntry.request_id,
        method: logEntry.method,
        path: logEntry.path,
        status_code: logEntry.status_code,
        latency_ms: logEntry.latency_ms,
        checksum: checksum,
        s3_key: s3Key,
        // เก็บเฉพาะ Metadata ที่ไม่มี PII
        metadata: {
          region: logEntry.region || 'unknown',
          service: logEntry.service || 'api-gateway',
          version: logEntry.version || 'v1'
        }
      }
    }));

    return { logId, s3Key, checksum };
  }

  // Query Log แบบ Decrypt
  async getLog(logId) {
    const result = await this.dynamo.send(new QueryCommand({
      TableName: this.tableName,
      KeyConditionExpression: 'log_id = :id',
      ExpressionAttributeValues: {
        ':id': logId
      }
    }));

    if (!result.Items || result.Items.length === 0) {
      return null;
    }

    const index = result.Items[0];
    
    // ดึง Log จาก S3
    const s3Response = await this.s3.send(new GetObjectCommand({
      Bucket: this.bucketName,
      Key: index.s3_key
    }));

    const encryptedData = JSON.parse(
      await s3Response.Body.transformToString()
    );

    // Decrypt Log
    const decrypted = await this.decryptLog(encryptedData);