🧩🧠 Zero Trust: vì sao Microservices không còn tin nhau? - Microservice Architecture P10
Zero Trust: vì sao Microservices không còn tin nhau?
1. Khi kẻ tấn công đã vào trong rồi, bạn làm gì?
Tháng 8 năm đó, team kỹ thuật nhận được một cảnh báo từ hệ thống monitoring: tài khoản ngân hàng của một số khách hàng bị trừ tiền mà không có giao dịch nào được khởi tạo từ phía người dùng.
Quá trình điều tra bắt đầu. Payment Service nhận được request hợp lệ từ một service nội bộ — không có gì bất thường cả. Request đến từ trong mạng, IP thuộc dải nội bộ, không có dấu hiệu xâm nhập từ bên ngoài. Nhưng khi truy ngược lại, kỹ sư phát hiện điều kỳ lạ: request đó đến từ reporting-service — một service chỉ làm nhiệm vụ tổng hợp số liệu thống kê, không có bất kỳ nghiệp vụ thanh toán nào.
Thực tế là reporting-service đã bị compromise. Kẻ tấn công khai thác một lỗ hổng trong một thư viện open-source mà service đó đang dùng, chiếm quyền điều khiển container, rồi từ đó bắt đầu gửi request thẳng sang payment-service. Và payment-service xử lý bình thường — bởi vì nó tin tưởng mọi request đến từ trong mạng nội bộ.
Đây không phải câu chuyện giả định. Đây là kịch bản xảy ra ở nhiều hệ thống hơn bạn nghĩ, với mức độ thiệt hại khác nhau. Và nguyên nhân cốt lõi thường chỉ là một câu giả định ngầm mà không ai viết ra nhưng toàn bộ kiến trúc đang dựa vào: "Mọi thứ bên trong mạng nội bộ đều đáng tin."
2. Niềm tin phổ biến: "HTTPS ở Gateway là đủ rồi"
Khi thiết kế một hệ thống Microservices, phần lớn team sẽ đặt một API Gateway ở biên giới — nơi tiếp nhận toàn bộ traffic từ bên ngoài. Gateway đó sẽ được cấu hình TLS để mã hóa kết nối, xác thực JWT từ người dùng, và rate limiting để chống tấn công.
Sau khi hoàn thành bước này, team thường cảm thấy yên tâm. Lý luận khá hợp lý: "Traffic từ bên ngoài đã được lọc và mã hóa. Còn traffic bên trong thì các service tin nhau, đó là môi trường nội bộ, không ai vào được."
Cái nhìn này có nguồn gốc từ mô hình bảo mật truyền thống gọi là Perimeter Security — tức là xây một bức tường kiên cố bao quanh, ai đã vào được bên trong thì mặc định được tin tưởng. Giống như một tòa nhà có bảo vệ ở cổng: một khi bạn qua được kiểm tra và vào trong, bạn có thể đi tới bất cứ phòng nào.
Mô hình này hoạt động tốt khi hệ thống còn nhỏ, khi số lượng service ít, và khi bề mặt tấn công còn hạn chế. Nhưng trong thế giới Microservices hiện đại, nó đã trở nên nguy hiểm.
3. Tại sao "tin tưởng nội bộ" vỡ ra trong môi trường Microservices
Vấn đề không phải là bức tường bên ngoài không đủ chắc. Vấn đề là bên trong tòa nhà đó có quá nhiều phòng, quá nhiều cửa, và không có khóa nào cả.
Trong một hệ thống Microservices trưởng thành, có thể có vài chục đến vài trăm service. Mỗi service có thể được viết bằng ngôn ngữ khác nhau, chạy trên runtime khác nhau, dùng thư viện khác nhau. Bề mặt tấn công không phải là một điểm — đó là tổng tất cả các service đang chạy.
Hãy nhìn vào thực tế của môi trường Kubernetes:
┌─────────────────────────────────────────────────────────────────┐
│ Kubernetes Cluster │
│ │
│ [API Gateway] ←──── HTTPS ──── Internet │
│ │ │
│ │ (sau đây là "tin tưởng hoàn toàn") │
│ ↓ │
│ [Order Svc] ──────────────────→ [Payment Svc] │
│ │ ↑ │
│ ↓ │ │
│ [Inventory Svc] [Reporting Svc] ──┘ ← BỊ COMPROMISE │
│ │ │
│ └──────────→ [User Data Svc] │
│ │
│ Mặc định: mọi Pod có thể gọi mọi Pod khác qua HTTP │
└─────────────────────────────────────────────────────────────────┘
Trong cấu hình mặc định của Kubernetes, mọi Pod trong cluster đều có thể gọi mọi Pod khác mà không cần bất kỳ xác thực nào. Nếu reporting-service bị compromise, kẻ tấn công có thể từ đó gọi thẳng sang payment-service, user-data-service, hay bất cứ service nào khác trong cluster.
Không có xác thực. Không có phân quyền. Chỉ cần biết địa chỉ nội bộ và cổng dịch vụ.
Đây không phải lý thuyết. Đây là hành vi mặc định của Kubernetes cho đến khi bạn chủ động thay đổi nó.
4. Góc nhìn mới: Zero Trust — Đừng tin ai, kể cả người trong nhà
Zero Trust không phải là một công cụ hay một sản phẩm. Đó là một nguyên tắc tư duy về bảo mật, được tóm gọn trong một câu:
"Never Trust, Always Verify."
Nếu Perimeter Security hỏi: "Người này có đến từ bên trong không?" thì Zero Trust hỏi: "Người này là ai, họ muốn làm gì, và họ có được phép làm việc đó không?" — bất kể họ đang đứng ở đâu.
Áp dụng vào Microservices, Zero Trust dựa trên ba nguyên tắc cốt lõi:
Nguyên tắc 1: Verify explicitly (Xác thực mọi cuộc gọi) Mỗi request đến một service phải chứng minh danh tính của người gửi. Không có ngoại lệ chỉ vì request đến từ IP nội bộ hay từ service quen thuộc.
Nguyên tắc 2: Least privilege (Quyền hạn tối thiểu)
Mỗi service chỉ được phép gọi đúng những service mà nó thực sự cần trong luồng nghiệp vụ của nó. Reporting Service không bao giờ có lý do để gọi Payment Service — vậy thì hãy chặn nó ở tầng hạ tầng, không chỉ ở tầng application logic.
Nguyên tắc 3: Assume breach (Giả định hệ thống luôn đang bị xâm nhập) Thiết kế với giả định rằng một service nào đó trong cluster đang bị compromise. Câu hỏi không phải là "liệu có bị hack không?" mà là "nếu service X bị compromise, thiệt hại tối đa có thể lan rộng tới đâu?"
5. mTLS — Xác thực hai chiều giữa các service
Công cụ kỹ thuật quan trọng nhất để hiện thực hóa Zero Trust giữa các service là Mutual TLS (mTLS).
TLS thông thường bạn đã quen: server chứng minh danh tính với client bằng certificate (đó là cơ chế HTTPS hoạt động). mTLS đi thêm một bước: cả hai phía đều phải chứng minh danh tính với nhau.
TLS thông thường:
Client → "Bạn là ai?"
Server → [Certificate] "Tôi là payment-service.internal"
Client → OK, tin tưởng. Gửi request.
mTLS:
Client → [Certificate] "Tôi là order-service. Bạn là ai?"
Server → [Certificate] "Tôi là payment-service. Bạn là ai?"
Cả hai xác thực certificate của nhau.
Kết nối chỉ được thiết lập nếu cả hai certificate đều hợp lệ.
Kết quả là: ngay cả khi kẻ tấn công đã nằm trong cluster và biết địa chỉ nội bộ của payment-service, họ vẫn không thể giao tiếp với nó nếu không có certificate hợp lệ được cấp bởi Certificate Authority của hệ thống.
Nhưng đây là điểm mà nhiều người e ngại: Quản lý certificate nghe có vẻ cực kỳ phức tạp. Phải tự tạo CA, phải phân phối cert tới từng service, phải renew trước khi hết hạn, phải revoke khi service bị xâm phạm...
Sự thật là: bạn không cần tự làm điều đó ở tầng ứng dụng.
Service Mesh — cụ thể là Istio hay Linkerd — xử lý toàn bộ vòng đời của certificate một cách tự động. Khi một Pod mới được tạo ra, sidecar proxy (Envoy trong trường hợp của Istio) tự động yêu cầu và nhận certificate từ hệ thống quản lý certificate nội bộ (Citadel/istiod). Certificate này được renew tự động trước khi hết hạn. Khi Pod bị xóa, certificate bị thu hồi.
Toàn bộ quá trình này diễn ra hoàn toàn trong suốt với application code. Service của bạn vẫn gọi HTTP như bình thường — nhưng ở tầng mạng, mọi cuộc giao tiếp đều đã được mã hóa và xác thực hai chiều bằng mTLS.
┌─────────────────────────────────────────────────────────────────┐
│ Pod: order-service │
│ ┌─────────────────┐ ┌──────────────────────────────────┐ │
│ │ App Container │────→│ Envoy Sidecar (Istio) │ │
│ │ (HTTP call) │ │ - mTLS outbound │ │
│ └─────────────────┘ │ - Certificate từ istiod │ │
│ └──────────────────────────────────┘ │
│ │ (mTLS encrypted) │
│ ↓ │
│ Pod: payment-service │
│ ┌─────────────────┐ ┌──────────────────────────────────┐ │
│ │ App Container │←────│ Envoy Sidecar (Istio) │ │
│ │ (nhận request) │ │ - mTLS inbound │ │
│ └─────────────────┘ │ - Xác thực cert của caller │ │
│ └──────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
Misconception bị phá vỡ: mTLS không đòi bạn viết thêm một dòng code nào ở tầng ứng dụng nếu bạn dùng service mesh. Đó là câu trả lời cho nỗi lo "triển khai mTLS rất phức tạp."
6. JWT Token Propagation — Truyền danh tính người dùng xuyên chuỗi service
mTLS giải quyết bài toán xác thực danh tính giữa các service. Nhưng có một bài toán khác: làm thế nào để các service bên trong biết request đang được thực hiện thay cho người dùng nào?
Khi người dùng Alice đăng nhập và gửi request tạo đơn hàng, API Gateway xác thực JWT token của Alice. Từ đó trở đi, request đi qua order-service → inventory-service → payment-service. Mỗi service cần biết: đây là request của Alice, với quyền hạn X, thuộc tenant Y.
Kỹ thuật để làm điều này gọi là Token Propagation: JWT của người dùng được trích xuất tại API Gateway và được forward theo trong header của mọi service call tiếp theo trong chuỗi.
API Gateway nhận JWT từ client:
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
order-service gọi payment-service:
POST /internal/payments
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
X-Service-Identity: order-service (từ mTLS cert)
payment-service khi nhận request sẽ có đủ hai lớp thông tin:
- Service identity từ mTLS certificate: "Caller này là
order-service, certificate hợp lệ" - User identity từ JWT: "Hành động này được thực hiện thay cho user
alice@example.comvới rolestandard_user"
Với hai lớp thông tin này, payment-service có thể thực hiện authorization đầy đủ: không chỉ xác nhận đây là một service hợp lệ gọi vào, mà còn kiểm tra user Alice có được phép thực hiện giao dịch này không.
7. Failure Case nguy hiểm: Token Propagation mù quáng
Tuy nhiên, Token Propagation cũng chứa một cái bẫy mà nhiều team vô tình rơi vào.
Hãy tưởng tượng hệ thống của bạn tích hợp với một analytics service bên thứ ba để ghi lại hành vi người dùng. Vì lý do tiện lợi, khi gọi API của service bên thứ ba đó, developer forward luôn JWT của người dùng vào header — giống hệt cách forward giữa các service nội bộ.
JWT của người dùng thường chứa:
{
"sub": "user_123",
"email": "alice@company.com",
"roles": ["admin", "finance"],
"phone": "+84901234567",
"exp": 1720000000
}
Khi bạn forward token này sang một service bên thứ ba, bạn vừa rò rỉ: email, số điện thoại, và toàn bộ danh sách quyền hạn của người dùng ra khỏi hệ thống. Nếu service bên thứ ba đó bị compromise, hay nếu họ log token để debug, dữ liệu cá nhân của người dùng đã thoát khỏi tầm kiểm soát của bạn.
Nguyên tắc phòng tránh: Khi gọi service bên thứ ba hoặc sang domain boundary khác, không forward JWT gốc. Thay vào đó, tạo một service-specific token chỉ chứa đúng thông tin mà service đó cần, không hơn không kém. Internal services trong cùng trust domain thì forward token nguyên vẹn; external boundary thì dùng token mới được tạo với phạm vi hạn chế.
8. Network Policy — Hàng rào cuối cùng ở tầng hạ tầng
mTLS và JWT giải quyết bài toán xác thực và phân quyền ở tầng ứng dụng. Nhưng Zero Trust đòi hỏi thêm một lớp: giới hạn kết nối mạng ở tầng hạ tầng.
Kubernetes Network Policies cho phép bạn định nghĩa tường lửa ở tầng Pod: Pod nào được phép nói chuyện với Pod nào, qua port nào, theo protocol nào.
Ví dụ logic của Network Policy:
payment-service chỉ chấp nhận inbound traffic từ:
- order-service (port 8080)
- refund-service (port 8080)
Tất cả traffic khác → DROP
reporting-service chỉ chấp nhận inbound từ:
- dashboard-service (port 3000)
reporting-service không được phép kết nối outbound đến:
- payment-service
- user-data-service
Khi một Network Policy như vậy được áp dụng, ngay cả khi reporting-service bị compromise hoàn toàn, kẻ tấn công vẫn không thể gửi được packet nào đến payment-service — bởi vì kernel của node sẽ drop packet đó trước khi nó đến được đích. Đây là defense-in-depth: ngay cả khi lớp xác thực ở tầng application bị bypass bằng cách nào đó, lớp mạng vẫn chặn lại.
┌──────────────────────────────────────────────────────┐
│ Defense in Depth: Zero Trust Layers │
│ │
│ Layer 1: Network Policy (Kubernetes / Calico) │
│ → Chặn kết nối không được phép ở tầng mạng │
│ │
│ Layer 2: mTLS (Service Mesh / Istio) │
│ → Xác thực danh tính service bằng certificate │
│ │
│ Layer 3: JWT Validation + AuthZ (Application) │
│ → Xác thực quyền hạn người dùng cuối │
│ │
│ Mỗi layer độc lập. Mỗi layer là một lần kiểm tra. │
└──────────────────────────────────────────────────────┘
9. Trade-offs: Zero Trust không miễn phí
Không có gì là free lunch trong kỹ thuật. Zero Trust đem lại lợi ích bảo mật rõ ràng, nhưng kèm theo những chi phí thực sự mà bạn phải cân nhắc.
Overhead về latency: mTLS thêm một TLS handshake vào mỗi kết nối mới giữa hai service. Trong môi trường có nhiều short-lived connections, điều này có thể tích lũy. Giải pháp phổ biến là connection pooling và keep-alive — nhưng đây là điều bạn phải chủ động cấu hình, không tự nhiên có.
Độ phức tạp vận hành tăng lên: Service mesh như Istio mạnh mẽ nhưng không đơn giản. Đội vận hành cần hiểu về Envoy proxy, istiod, VirtualService, DestinationRule, PeerAuthentication. Learning curve không nhỏ. Nhiều team áp dụng Istio rồi gặp khó khăn trong việc debug khi network policy chặn connection mà không rõ tại sao.
Certificate management phải được giám sát: Mặc dù service mesh tự động hóa phần lớn, bạn vẫn phải giám sát: CA certificate của hệ thống có hết hạn không? Quy trình bootstrap certificate khi cluster bị tái tạo có hoạt động đúng không? Sự cố certificate hết hạn trong production có thể khiến toàn bộ internal traffic bị chặn — một kiểu outage rất khó debug nếu bạn chưa từng gặp.
AuthZ logic bị phân tán: Khi mỗi service phải tự kiểm tra JWT và thực hiện authorization, logic phân quyền bắt đầu bị nhân bản ở nhiều nơi. Một cách tiếp cận trưởng thành hơn là dùng một Policy Engine tập trung (như OPA — Open Policy Agent) để định nghĩa toàn bộ authorization rules ở một chỗ, rồi các service query policy engine đó. Nhưng đây lại tạo thêm một dependency và một điểm có thể fail.
10. Khi nào Zero Trust có thể phản tác dụng?
Có những tình huống mà Zero Trust triển khai không đúng cách lại gây ra vấn đề mới:
Cấu hình Network Policy quá chặt trong giai đoạn phát triển: Developer không thể kết nối từ môi trường local sang staging service để debug. Họ bắt đầu tìm cách "mở tạm" rồi quên đóng lại. Kết quả là production có Network Policy nhưng staging thì không — mất đi môi trường kiểm thử chính xác.
Certificate rotation đồng bộ không đúng: Khi CA root certificate được rotate, nếu không phải tất cả service cùng cập nhật đồng thời, có thể xảy ra tình trạng một số service chấp nhận cert mới, một số service vẫn cần cert cũ. Kết quả là một số cặp service không thể giao tiếp với nhau trong thời gian chuyển tiếp.
Token Propagation sang service bên thứ ba là một trong những lỗi phổ biến nhất và nguy hiểm nhất. Dữ liệu cá nhân người dùng bị rò rỉ không phải do bị tấn công từ bên ngoài, mà do thiết kế vội vã của chính team nội bộ.
11. Tổng kết: Bảo mật là thuộc tính kiến trúc, không phải tính năng bổ sung
Quay lại câu chuyện ban đầu. reporting-service bị compromise và hacker tự do gọi sang payment-service. Nếu hệ thống đó được thiết kế theo Zero Trust:
- Network Policy sẽ drop mọi packet từ
reporting-serviceđếnpayment-servicengay tại tầng kernel — kẻ tấn công không thể thiết lập kết nối. - Ngay cả nếu somehow kết nối được thiết lập, mTLS sẽ yêu cầu certificate hợp lệ — certificate của
reporting-servicekhông được phép gọipayment-servicetheo policy đã cấu hình. - Ngay cả nếu mTLS bị bypass, JWT validation và AuthZ tại
payment-servicesẽ từ chối request vì không có user context hợp lệ.
Ba lớp bảo vệ độc lập. Mỗi lớp là một rào cản riêng. Kẻ tấn công phải vượt qua tất cả ba lớp — và điều này làm tăng chi phí tấn công lên đáng kể.
Ba bài học cốt lõi từ Episode này:
-
Perimeter Security đã lỗi thời trong thế giới Microservices. Khi bề mặt tấn công là hàng chục service, mỗi service là một điểm có thể bị compromise, thì mô hình "tin tưởng bên trong" không còn là giả định hợp lý nữa.
-
mTLS không đòi bạn viết code phức tạp. Service mesh giải quyết vòng đời certificate tự động. Điều bạn cần đưa ra là quyết định kiến trúc: chọn service mesh nào và cấu hình policy ra sao — không phải tự viết certificate management.
-
Token Propagation phải có boundary rõ ràng. Forward JWT giữa internal services trong cùng trust domain là hợp lý. Forward JWT gốc ra ngoài domain boundary hoặc sang third-party service là rò rỉ dữ liệu có kiểm soát.
Lời kết: Khi bảo mật đã vào trong kiến trúc, câu hỏi tiếp theo là gì?
Khi bạn đã giải quyết được bài toán quan sát hệ thống (Episode 9) và bảo mật giao tiếp nội bộ (Episode này), phần lớn kỹ sư bắt đầu nghĩ đến hạ tầng và triển khai. Container, Docker, Kubernetes — những thứ mà nhiều người đang nghĩ rằng chúng chính là Microservices.
Nhưng đó là một hiểu lầm đáng được nhìn thẳng vào.
Docker không tạo ra Microservices. Container không phải là service boundary. Và việc chạy một monolith trong container không làm nó trở thành Microservices. Episode tiếp theo sẽ vạch trần hiểu lầm kinh điển này — và lý do tại sao nhiều team đang nhầm lẫn giữa công cụ triển khai và kiến trúc phần mềm.
Góc nhìn thêm: Zero Trust trong microservices không bắt đầu từ bảo mật, mà bắt đầu từ sự khiêm tốn
Zero Trust buộc hệ thống chấp nhận một sự thật khó chịu: chỉ vì request đến từ mạng nội bộ không có nghĩa là request đó đáng tin. Một service bị xâm nhập, một secret bị lộ, một policy bị cấu hình sai là đủ để "internal traffic" trở thành đường tấn công hiệu quả nhất.
Vì thế, hệ thống trưởng thành phải giả định:
- nội bộ cũng có thể độc hại
- identity phải được xác minh ở từng hop quan trọng
- permission phải tối thiểu theo đúng nhu cầu
- audit trail phải đủ để truy dấu hành vi bất thường
Nhìn thêm: Security trade-off mà team thường tránh nhìn thẳng
Zero Trust làm mọi thứ chặt hơn, nhưng cũng làm vận hành nặng hơn:
- rotate certs và secrets trở thành công việc thật
- latency tăng nhẹ do thêm lớp xác minh
- debugging khó hơn vì request path có nhiều enforcement point
- local development phức tạp hơn đáng kể
Đây là chỗ team phải trung thực với mình. Nếu không muốn trả giá vận hành, đừng dùng ngôn ngữ "zero trust" như một slogan. Nhưng nếu hệ thống đã đủ nhạy cảm, cái giá không làm mới là thứ đắt hơn nhiều.
🤝 Đồng hành cùng TechCraft
TechCraft là nơi chia sẻ kiến thức về Backend Engineering, Database, Distributed Systems và Production Architecture thông qua các bài viết, video và những series được xây dựng theo lộ trình.
Nếu bạn yêu thích cách tiếp cận này, hãy tiếp tục đồng hành cùng TechCraft trên các nền tảng bên dưới.
Và nếu muốn học chuyên sâu hơn, Dev Insider sẽ là nơi tập trung toàn bộ các nội dung premium được cập nhật liên tục mỗi tuần.
🚀 Dev Insider https://www.patreon.com/techcraft_official/posts/vi-sao-dev-ra-161163881?collection=2220113
📘 Facebook https://www.facebook.com/techcraft.official
🎥 YouTube https://www.youtube.com/@techcraft.official
🎵 TikTok https://www.tiktok.com/@techcraft.official
Think Beyond Code. Build Better Systems.
All rights reserved