Tác giả: HolySheep AI Team — Chuyên gia tích hợp API tài chính số
Mở đầu: Câu chuyện thật từ một dự án bị giữ tiền
Tháng 3/2025, một đội ngũ phát triển game blockchain tại Việt Nam gặp phải tình huống kinh hoàng: $480,000 USDT bị khóa trong ví multisig của OKX. Nguyên nhân? Một developer mới vô tình có quyền API withdrawal đầy đủ đã trigger một giao dịch test vào đúng thời điểm hệ thống giám sát bảo trì.
May mắn họ phát hiện sớm và nhờ hỗ trợ từ OKX để freeze giao dịch. Tuy nhiên, sự cố này cho thấy một thực tế: hầu hết các team không phân tách quyền API đúng cách khi vận hành ví đa chữ ký cho doanh nghiệp.
Bài viết này sẽ hướng dẫn chi tiết cách cấu hình phân quyền API multi-signature cho Binance, OKX và Bybit — giúp bạn xây dựng hệ thống kiểm soát rủi ro chặt chẽ như các quỹ đầu tư chuyên nghiệp.
Multi-Signature Wallet là gì và tại sao cần phân tách quyền
Multi-signature (multisig) wallet yêu cầu nhiều hơn một chữ ký để thực hiện giao dịch. Thay vì một private key đơn lẻ có thể bị compromise, multisig phân tán rủi ro cho nhiều người giữ khóa.
3 mô hình multisig phổ biến
| Mô hình | Cấu hình | Use case | Rủi ro |
|---|---|---|---|
| M-of-N Standard | 2-of-3, 3-of-5 | Quỹ công ty, DAO treasury | Trung bình |
| Role-Based Access | Admin/Operator/Viewer | Exchange, payment gateway | Thấp |
| Time-Locked Recovery | 24h delay + override key | High-value storage | Rất thấp |
Trong bài viết này, chúng ta tập trung vào Role-Based Access Control (RBAC) được triển khai qua API của các sàn giao dịch lớn.
Kiến trúc hệ thống đề xuất
┌─────────────────────────────────────────────────────────────────┐
│ ENTERPRISE TREASURY SYSTEM │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ VIEWER │ │ OPERATOR │ │ ADMIN │ │
│ │ API Key │ │ API Key │ │ API Key │ │
│ │ Read-only │ │ Withdraw │ │ All Access │ │
│ │ │ │ (Limited) │ │ + Settings │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
│ └───────────────────┼───────────────────┘ │
│ │ │
│ ┌────────▼────────┐ │
│ │ APPROVAL │ │
│ │ GATEWAY │ │
│ │ (Custom Logic) │ │
│ └────────┬────────┘ │
│ │ │
│ ┌───────────────────┼───────────────────┐ │
│ │ │ │ │
│ ┌──────▼──────┐ ┌──────▼──────┐ ┌──────▼──────┐ │
│ │ BINANCE │ │ OKX │ │ BYBIT │ │
│ │ Sub-Account │ │ Master │ │ Unified │ │
│ │ + Sub-User │ │ Account │ │ Account │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
Phần 1: Binance Sub-Account với Phân Quyền API
Bước 1: Tạo Master Account API Key
Đăng nhập Binance vào Dashboard → API Management → Create API. Chọn loại System (generated) để Binance tự sinh key thay vì tự quản lý.
# ============================================
BINANCE: Tạo Sub-Account với API Key riêng
============================================
Base URL: https://api.binance.com
import requests
import hashlib
import hmac
import time
BINANCE_API_KEY = "YOUR_BINANCE_API_KEY"
BINANCE_SECRET_KEY = "YOUR_BINANCE_SECRET_KEY"
BASE_URL = "https://api.binance.com"
def binance_signature(params, secret_key):
"""Tạo signature cho Binance API"""
query_string = '&'.join([f"{k}={v}" for k, v in params.items()])
signature = hmac.new(
secret_key.encode('utf-8'),
query_string.encode('utf-8'),
hashlib.sha256
).hexdigest()
return signature
def create_sub_account(email, api_key, ip_whitelist):
"""
Tạo sub-account với quyền hạn chế
- Spot trading: enabled
- Withdrawal: DISABLED by default
- Internal transfer: enabled
"""
endpoint = "/sapi/v1/sub-account/virtualSubAccount"
params = {
"email": email,
"recvWindow": 5000,
"timestamp": int(time.time() * 1000)
}
params["signature"] = binance_signature(params, BINANCE_SECRET_KEY)
headers = {
"X-MBX-APIKEY": BINANCE_API_KEY,
"Content-Type": "application/x-www-form-urlencoded"
}
response = requests.post(
f"{BASE_URL}{endpoint}",
headers=headers,
data=params
)
return response.json()
Ví dụ: Tạo sub-account cho Trading Bot
result = create_sub_account(
email="[email protected]",
api_key="trading-bot-key",
ip_whitelist="203.0.113.0/24" # Chỉ cho phép IP nội bộ
)
print(result)
Bước 2: Cấu hình Sub-Account Permissions
# ============================================
BINANCE: Phân quyền chi tiết cho Sub-Account
============================================
def set_sub_account_permissions(master_api_key, sub_email, permissions):
"""
Cấu hình permissions chi tiết cho sub-account
Permissions available:
- enableSpotTrading: bool
- enableMarginTrading: bool
- enableFuturesTrading: bool
- enableVanillaOptions: bool
- enableSubAccountApiKey": bool
- enableUniversalTransfer: bool
- enableDerivatives: bool
- enableWithdrawals: bool (Rất quan trọng!)
"""
endpoint = "/sapi/v1/sub-account/updateUserPermission"
params = {
"subEmail": sub_email,
"canTrade": permissions.get("canTrade", True),
"canDeposit": permissions.get("canDeposit", False),
"canWithdraw": permissions.get("canWithdraw", False), # Mặc định OFF
"canInternalTransfer": permissions.get("canInternalTransfer", True),
"recvWindow": 5000,
"timestamp": int(time.time() * 1000)
}
params["signature"] = binance_signature(params, BINANCE_SECRET_KEY)
headers = {"X-MBX-APIKEY": BINANCE_API_KEY}
response = requests.post(f"{BASE_URL}{endpoint}", headers=headers, data=params)
return response.json()
def create_sub_api_key(sub_email, permissions):
"""
Tạo API key riêng cho sub-account với permissions cụ thể
"""
endpoint = "/sapi/v1/sub-account/apiRestriction"
params = {
"email": sub_email,
"subAccountApiKey": "", # Để trống = tạo mới
"permissions": {
"enableSpotAndMarginTrading": permissions.get("spot", False),
"enable futures": permissions.get("futures", False),
"enableWallet": permissions.get("wallet", False),
"enableWithdrawals": permissions.get("withdraw", False), # LUÔN = False cho bot
"enableInternalTransfer": permissions.get("internal_transfer", True),
"enableVanillaOptions": permissions.get("options", False),
"enableReading": permissions.get("read_only", True),
},
"ipRestrict": True,
"allowedIps": "203.0.113.0/24,198.51.100.0/24", # Whitelist IPs
"recvWindow": 5000,
"timestamp": int(time.time() * 1000)
}
params["signature"] = binance_signature(params, BINANCE_SECRET_KEY)
response = requests.post(f"{BASE_URL}{endpoint}", headers=headers, data=params)
return response.json()
============================================
VÍ DỤ THỰC TẾ: Cấu hình cho 3 vai trò
============================================
1. VIEWER - Chỉ đọc, không thao tác
viewer_config = {
"spot": False,
"futures": False,
"wallet": False,
"withdraw": False,
"internal_transfer": False,
"options": False,
"read_only": True
}
2. OPERATOR - Giao dịch được, rút tiền HẠN CHẾ
operator_config = {
"spot": True,
"futures": True,
"wallet": False, # Không rút tiền trực tiếp
"withdraw": False, # Chỉ qua internal transfer
"internal_transfer": True, # Chuyển sang ví chính
"options": False,
"read_only": False
}
3. ADMIN - Full access nhưng vẫn cần multi-approval
admin_config = {
"spot": True,
"futures": True,
"wallet": True,
"withdraw": True, # Cần ít nhất 2/3 keys
"internal_transfer": True,
"options": True,
"read_only": False
}
Áp dụng cấu hình
viewer_result = create_sub_api_key("[email protected]", viewer_config)
operator_result = create_sub_api_key("[email protected]", operator_config)
admin_result = create_sub_api_key("[email protected]", admin_config)
print(f"Viewer API: {viewer_result}")
print(f"Operator API: {operator_result}")
print(f"Admin API: {admin_result}")
Phần 2: OKX Account API với Multi-Permission
Tạo Sub-Account và API Key với OKX
# ============================================
OKX: Multi-SubAccount với RBAC
====================================
Base URL: https://www.okx.com
import jwt
import datetime
OKX_API_KEY = "YOUR_OKX_API_KEY"
OKX_SECRET_KEY = "YOUR_OKX_SECRET_KEY"
OKX_PASSPHRASE = "YOUR_OKX_PASSPHRASE"
BASE_URL = "https://www.okx.com"
def okx_sign(timestamp, method, path, body, secret_key):
"""Tạo signature cho OKX API v2"""
message = timestamp + method + path + (body if body else "")
mac = hmac.new(
secret_key.encode('utf-8'),
message.encode('utf-8'),
hashlib.sha256
).digest()
return base64.b64encode(mac).decode()
def okx_headers(method, path, body=None):
"""Tạo headers cho OKX API v5"""
timestamp = datetime.datetime.utcnow().isoformat() + 'Z'
signature = okx_sign(timestamp, method, path, body, OKX_SECRET_KEY)
return {
'Content-Type': 'application/json',
'OK-ACCESS-KEY': OKX_API_KEY,
'OK-ACCESS-SIGN': signature,
'OK-ACCESS-TIMESTAMP': timestamp,
'OK-ACCESS-PASSPHRASE': OKX_PASSPHRASE,
}
def create_sub_account(sub_account_name, label):
"""
Tạo sub-account trên OKX
"""
endpoint = "/api/v5/users/create-subaccount"
payload = {
"subAccount": sub_account_name,
"label": label
}
headers = okx_headers("POST", endpoint, json.dumps(payload))
response = requests.post(
f"{BASE_URL}{endpoint}",
headers=headers,
data=json.dumps(payload)
)
return response.json()
def set_sub_account_permission(sub_account, permissions):
"""
Cấu预设权限 cho sub-account
Permission values:
- "readOnly": chỉ đọc
- "trade": giao dịch
- "withdraw": rút tiền
- "transfer": chuyển tiền nội bộ
"""
endpoint = "/api/v5/users/subaccount/set-permission"
payload = {
"subAccount": sub_account,
"perm": permissions.get("perm", "readOnly"),
"apikey": {
"apiKey": permissions.get("apiKey", ""),
"perm": permissions.get("apiPerm", "readOnly"),
"ipWhitelist": permissions.get("ipWhitelist", [])
}
}
headers = okx_headers("POST", endpoint, json.dumps(payload))
response = requests.post(
f"{BASE_URL}{endpoint}",
headers=headers,
data=json.dumps(payload)
)
return response.json()
============================================
VÍ DỤ: Cấu hình 3-tier permission
============================================
1. Tạo 3 sub-accounts
sub_accounts = {
"viewer": {
"name": "vault-viewer-01",
"label": "Treasury Viewer - Read Only"
},
"operator": {
"name": "vault-operator-01",
"label": "Treasury Operator - Limited Trade"
},
"admin": {
"name": "vault-admin-01",
"label": "Treasury Admin - Full Control"
}
}
Tạo sub-accounts
for role, config in sub_accounts.items():
result = create_sub_account(config["name"], config["label"])
print(f"Created {role}: {result}")
2. Cấu hình permissions chi tiết
permission_configs = {
"viewer": {
"perm": "readOnly",
"apiKey": "",
"apiPerm": "readOnly",
"ipWhitelist": ["203.0.113.0/24"]
},
"operator": {
"perm": "trade",
"apiKey": "",
"apiPerm": "trade",
"ipWhitelist": ["203.0.113.0/24", "198.51.100.0/24"]
},
"admin": {
"perm": "trade,withdraw,transfer",
"apiKey": "",
"apiPerm": "trade,withdraw,transfer",
"ipWhitelist": ["203.0.113.0/24", "198.51.100.0/24", "192.0.2.0/24"]
}
}
Áp dụng permissions
for role, perms in permission_configs.items():
sub_acct = sub_accounts[role]["name"]
result = set_sub_account_permission(sub_acct, perms)
print(f"Permission {role}: {result}")
Phần 3: Bybit Unified Account API
Unified Account với Permission Groups
# ============================================
BYBIT: Unified Account với API Permission
====================================
Base URL: https://api.bybit.com
BYBIT_API_KEY = "YOUR_BYBIT_API_KEY"
BYBIT_SECRET_KEY = "YOUR_BYBIT_SECRET_KEY"
BASE_URL = "https://api.bybit.com"
def bybit_sign(api_key, timestamp, recv_window, param_str, secret_key):
"""Tạo signature cho Bybit API v3"""
message = str(timestamp) + api_key + recv_window + param_str
mac = hmac.new(
secret_key.encode('utf-8'),
message.encode('utf-8'),
hashlib.sha256
).digest()
return mac.hex()
def create_unified_api(sub_uid, api_key_label, permissions):
"""
Tạo API key cho unified account với permissions
Permission flags:
- readOnly: chỉ đọc dữ liệu
- trade: giao dịch spot/futures
- withdraw: rút tiền (cần 2FA bổ sung)
- transfer: chuyển tiền nội bộ
- unified: quản lý tài khoản
"""
endpoint = "/v5/user/create-sub-api"
params = {
"subUid": sub_uid,
"apiKey": api_key_label,
"permissions": {
"readOnly": permissions.get("readOnly", True),
"trade": permissions.get("trade", False),
"contractTrade": permissions.get("contractTrade", False),
"wallet": permissions.get("wallet", False),
"withdraw": permissions.get("withdraw", False),
"isMaintMode": permissions.get("isMaintMode", False),
"transfer": permissions.get("transfer", False),
"usdtPerm": permissions.get("usdtPerm", False),
},
"ips": permissions.get("ips", ["203.0.113.0/24"]),
"validDate": permissions.get("validDate", 90), # Hết hạn sau 90 ngày
"recvWindow": 5000,
"timestamp": int(time.time() * 1000)
}
param_str = json.dumps(params)
timestamp = int(time.time() * 1000)
recv_window = "5000"
sign = bysign(BYBIT_API_KEY, timestamp, recv_window, param_str, BYBIT_SECRET_KEY)
headers = {
"Content-Type": "application/json",
"X-BAPI-API-KEY": BYBIT_API_KEY,
"X-BAPI-SIGN": sign,
"X-BAPI-SIGN-TYPE": "2",
"X-BAPI-TIMESTAMP": str(timestamp),
"X-BAPI-RECV-WINDOW": recv_window
}
response = requests.post(f"{BASE_URL}{endpoint}", headers=headers, data=param_str)
return response.json()
============================================
CẤU HÌNH THỰC TẾ: 3 vai trò cho Bybit
============================================
VIEWER: Chỉ đọc, không thao tác gì
viewer_bybit = {
"readOnly": True,
"trade": False,
"contractTrade": False,
"wallet": False,
"withdraw": False,
"transfer": False,
"usdtPerm": False,
"ips": ["203.0.113.0/24"],
"validDate": 365 # Dài hạn cho monitoring
}
OPERATOR: Giao dịch được, không rút tiền
operator_bybit = {
"readOnly": False,
"trade": True,
"contractTrade": True,
"wallet": True, # Xem ví
"withdraw": False, # KHÔNG BAO GIỜ rút tiền trực tiếp
"transfer": True, # Chỉ chuyển nội bộ
"usdtPerm": True,
"ips": ["203.0.113.0/24", "198.51.100.0/24"],
"validDate": 90 # Hết hạn sau 90 ngày
}
ADMIN: Full access nhưng giới hạn IP và có 2FA
admin_bybit = {
"readOnly": False,
"trade": True,
"contractTrade": True,
"wallet": True,
"withdraw": True, # Cần thêm 2FA verification
"transfer": True,
"usdtPerm": True,
"ips": ["203.0.113.0/24", "198.51.100.0/24"],
"validDate": 30 # Hết hạn nhanh, cần renew định kỳ
}
Tạo API keys
viewer_result = create_unified_api("VIEWER_UID", "vault-viewer", viewer_bybit)
operator_result = create_unified_api("OPERATOR_UID", "vault-operator", operator_bybit)
admin_result = create_unified_api("ADMIN_UID", "vault-admin", admin_bybit)
print("Bybit API Setup Results:")
print(f"Viewer: {viewer_result}")
print(f"Operator: {operator_result}")
print(f"Admin: {admin_result}")
Approval Gateway: Triển khai Multi-Approval Logic
Sau khi có API keys với permissions riêng biệt, bạn cần một Approval Gateway để enforce quy trình multi-approval cho các giao dịch lớn.
# ============================================
APPROVAL GATEWAY: Multi-Signature Request Flow
============================================
import redis
from typing import List, Dict
from dataclasses import dataclass
from enum import Enum
class TransactionType(Enum):
SMALL_WITHDRAW = "small" # < $10,000
MEDIUM_WITHDRAW = "medium" # $10,000 - $100,000
LARGE_WITHDRAW = "large" # > $100,000
CRITICAL = "critical" # > $500,000
@dataclass
class ApprovalRequest:
request_id: str
requester_id: str
amount: float
currency: str
destination: str
required_approvals: int
approvals: List[str]
status: str
created_at: float
expires_at: float
class MultiSigApprovalGateway:
"""
Gateway quản lý multi-approval cho các giao dịch lớn
"""
def __init__(self, redis_host="localhost", redis_port=6379):
self.redis = redis.Redis(host=redis_host, port=redis_port, decode_responses=True)
self.approval_thresholds = {
TransactionType.SMALL_WITHDRAW: 1, # 1 người approve
TransactionType.MEDIUM_WITHDRAW: 2, # 2 người approve
TransactionType.LARGE_WITHDRAW: 3, # 3 người approve
TransactionType.CRITICAL: 5, # 5 người + CEO sign
}
def classify_transaction(self, amount_usd: float) -> TransactionType:
"""Phân loại giao dịch dựa trên số tiền"""
if amount_usd >= 500000:
return TransactionType.CRITICAL
elif amount_usd >= 100000:
return TransactionType.LARGE_WITHDRAW
elif amount_usd >= 10000:
return TransactionType.MEDIUM_WITHDRAW
else:
return TransactionType.SMALL_WITHDRAW
def create_withdrawal_request(
self,
requester_id: str,
amount: float,
currency: str,
destination: str,
exchange: str,
api_key: str
) -> Dict:
"""
Tạo yêu cầu rút tiền - tự động xác định số approvals cần thiết
"""
tx_type = self.classify_transaction(amount)
required = self.approval_thresholds[tx_type]
request = ApprovalRequest(
request_id=f"req_{uuid.uuid4().hex[:12]}",
requester_id=requester_id,
amount=amount,
currency=currency,
destination=destination,
required_approvals=required,
approvals=[],
status="pending",
created_at=time.time(),
expires_at=time.time() + 3600 # 1 giờ
)
# Lưu vào Redis
self.redis.setex(
f"approval:{request.request_id}",
3600,
json.dumps(request.__dict__)
)
# Gửi notification
self._notify_approvers(request, tx_type)
return {
"request_id": request.request_id,
"required_approvals": required,
"transaction_type": tx_type.value,
"status": "pending",
"expires_in": 3600
}
def approve_request(self, request_id: str, approver_id: str) -> Dict:
"""
Approve một yêu cầu - kiểm tra xem đã đủ chữ ký chưa
"""
key = f"approval:{request_id}"
data = self.redis.get(key)
if not data:
return {"error": "Request not found or expired"}
request = json.loads(data)
# Kiểm tra approver có quyền không
if approver_id in request["approvals"]:
return {"error": "Already approved by this approver"}
# Thêm approval
request["approvals"].append(approver_id)
# Kiểm tra đủ số lượng chưa
if len(request["approvals"]) >= request["required_approvals"]:
request["status"] = "approved"
self.redis.setex(key, 300, json.dumps(request)) # 5 phút để execute
return {
"status": "approved",
"can_execute": True,
"execution_window": 300
}
else:
remaining = request["required_approvals"] - len(request["approvals"])
self.redis.setex(key, 3600, json.dumps(request))
return {
"status": "pending",
"approvals": request["approvals"],
"remaining": remaining
}
def execute_withdrawal(self, request_id: str, exchange: str) -> Dict:
"""
Thực hiện withdrawal sau khi đủ approvals
"""
key = f"approval:{request_id}"
data = self.redis.get(key)
if not data:
return {"error": "Request expired"}
request = json.loads(data)
if request["status"] != "approved":
return {"error": "Request not approved"}
# Thực hiện withdrawal qua exchange API
result = self._call_exchange_api(request, exchange)
# Cleanup
self.redis.delete(key)
return result
============================================
SỬ DỤNG: Ví dụ flow cho withdrawal $250,000
============================================
gateway = MultiSigApprovalGateway()
1. Tạo yêu cầu rút $250,000
request = gateway.create_withdrawal_request(
requester_id="bot-trading-01",
amount=250000,
currency="USDT",
destination="0x742d35Cc6634C0532925a3b844Bc9e7595f3f234",
exchange="binance",
api_key="binance_operator_key"
)
print(f"Request created: {request}")
Output: {
"request_id": "req_a1b2c3d4e5f6",
"required_approvals": 3,
"transaction_type": "large",
"status": "pending",
"expires_in": 3600
}
2. 3 approvers approve lần lượt
approvers = ["[email protected]", "[email protected]", "[email protected]"]
for approver in approvers:
result = gateway.approve_request(request["request_id"], approver)
print(f"Approval from {approver}: {result}")
3. Sau khi đủ 3 approvals, execute
execution = gateway.execute_withdrawal(request["request_id"], "binance")
print(f"Execution result: {execution}")
Lỗi thường gặp và cách khắc phục
Lỗi 1: API Key không có quyền Withdrawal
# ============================================
LỖI: "81001 - Withdrawal is not allowed for this account"
============================================
"""
NGUYÊN NHÂN:
- API key được tạo với permission mặc định không có withdraw
- Trên Binance: sub-account có thể bị giới hạn bởi master account
- Trên OKX: sub-account cần enable withdrawal permission riêng
- Trên Bybit: unified account cần kích hoạt withdrawal permission
GIẢI PHÁP:
"""
=== Bước 1: Kiểm tra quyền hiện tại của API Key ===
def check_api_permissions(exchange, api_key):
"""Kiểm tra permissions của API key"""
if exchange == "binance":
# Binance: Query sub-account API key permissions
endpoint = "/sapi/v1/sub-account/apiRestriction"
# Gọi API với master key
elif exchange == "okx":
# OKX: Query sub-account permissions
endpoint = "/api/v5/users/subaccount/permission"
elif exchange == "bybit":
# Bybit: Query API key info
endpoint = "/v5/user/query-api"
return permissions
=== Bước 2: Cập nhật permissions ===
Binance: Update sub-account permissions
def enable_withdrawal_binance(sub_email):
"""Bật withdrawal cho sub-account Binance"""
endpoint = "/sapi/v1/sub-account/updateUserPermission"
params = {
"subEmail": sub_email,
"canWithdraw": True, # Bật withdrawal
"canInternalTransfer": True,
"canTrade": True,
"timestamp": int(time.time() * 1000)
}
# ... rest of signature logic
OKX: Enable withdrawal permission
def enable_withdrawal_okx(sub_account, api_key):
"""Bật withdrawal cho sub-account OKX"""
endpoint = "/api/v5/users/subaccount/set-permission"
payload = {
"subAccount": sub_account,
"perm": "trade,withdraw,transfer", # Thêm withdraw
"apikey": {
"apiKey": api_key,
"perm": "trade,withdraw,transfer"
}
}
# ... rest of request logic
Bybit: Update API key permissions
def enable_withdrawal_bybit(api_key):
"""Bật withdrawal cho Bybit unified API"""
endpoint = "/v5/user/update-api"
payload = {
"apiKey": api_key,
"permissions": {
"withdraw": True,
"readOnly": False,
"trade": True
}
}
# ... rest of request logic
Lỗi 2: IP Whitelist không khớp
# ============================================
LỖI: "IP address not in whitelist" hoặc "Invalid IP"
============================================
"""
NGUYÊN NHÂN THƯỜNG GẶP:
1. Server của bạn có dynamic IP
2. Cloud provider thay đổi IP khi restart instance
3. Request đến từ IP khác với whitelist
4. Format IP whitelist không đúng (thiếu /32, sai CIDR)
GIẢI PHÁP:
"""
=== Giải pháp 1: Sử dụng Static IP qua Cloud NAT ===
def setup_static