TL;DR
IdP (Part 5) giải quyết xác thực cho client là con người, có browser, có người chờ MFA. Nhưng nhiều client trong hệ thống enterprise không phải con người: CI/CD runner, cron job, probe giám sát, thiết bị IoT, SDK gọi API. Họ không có browser, không nhập mật khẩu được.
Cloudflare Access có 2 cơ chế cho non-human:
- Service token: 2 header HTTP (
CF-Access-Client-Id+CF-Access-Client-Secret). Thiết lập 5 phút. Rà soát qua log. - mTLS: client trình cert, Cloudflare xác minh chuỗi dựa trên root CA bạn upload. Thiết lập cần PKI. Mạnh hơn khi tổ chức có PKI sẵn hoặc cần định danh thiết bị.
Bài này đi qua:
- Khi nào dùng service token, khi nào mTLS.
- Thiết lập đầu-cuối cho cả hai.
- Chiến lược rotate với cửa sổ chồng lấn để không gián đoạn.
- Log kiểm toán cho traffic non-human.
- 4 anti-pattern phổ biến.
Luận điểm chính:
Service token là “API key có log”. mTLS là “định danh thiết bị có bằng chứng mật mã”. Đừng dùng service token cho cái cần bằng chứng. Đừng dùng mTLS cho cái không cần.
Bài này là Part 6 của Cloudflare One Handbook.
Dành cho ai
- DevOps/SRE thiết lập CI/CD, cron job, giám sát chạm tới app đứng sau Access.
- Kỹ sư nền tảng xây SDK/firmware thiết bị gọi về endpoint được bảo vệ.
- Kỹ sư bảo mật thiết kế zero-trust cho toàn bộ traffic, không chỉ con người.
Bạn nên đã đọc:
- Part 4, Access ZTNA cơ bản (đánh giá policy)
- Part 5, IdP integration (để hiểu phần “con người” là gì)
Sau bài này bạn sẽ:
- Biết chọn service token hay mTLS cho từng trường hợp.
- Thiết lập được cả hai đầu-cuối với ví dụ code thực.
- Có playbook rotate không gây gián đoạn.
- Biết rà soát traffic non-human trong log Zero Trust.
Bài này không nói về gì
- mTLS phía origin (client cert giữa Cloudflare ↔ origin): khác với client-side mTLS trong bài này.
- API token của Cloudflare platform (quản lý zone, DNS): hoàn toàn khác, không liên quan Access.
- OAuth client credentials flow: Cloudflare Access không hỗ trợ luồng này native; pattern tương đương là service token.
Khái niệm
- Service token: cặp (Client ID, Client Secret) do Cloudflare sinh. Client gửi qua 2 header:
CF-Access-Client-Id,CF-Access-Client-Secret. Cloudflare xác minh rồi khớp chính sách. - mTLS (mutual TLS): cả 2 bên bắt tay TLS đều trình cert. Client trình cert, Cloudflare xác minh chuỗi lên root CA đã tin.
- Root CA: cert của Certificate Authority bạn upload vào Cloudflare. Mọi cert client phải chuỗi lên root này mới hợp lệ.
- Chuỗi cert: chuỗi cert: client cert → intermediate CA → root CA.
- Action
Service Auth: action đặc biệt trong chính sách Access để khớp service token, không phải người dùng. - Định danh non-human: identity không gắn với người dùng trong IdP. Gắn với khối lượng công việc (service, device, CI job).
Human vs non-human: khác nhau ở đâu
Vì sao không dùng IdP cho bot?
- Không có browser: endpoint authorize của IdP gửi 302 redirect → cron/script không theo được.
- Không tương tác MFA: IdP thường yêu cầu MFA bổ sung. Bot không prompt được.
- Không có danh tính ở IdP: tạo người dùng
github-actions-bot@example.comtrong Okta = tốn một giấy phép, và người dùng đó dễ bị kẻ xấu lợi dụng hơn là bot thật dùng. - Rotate token: mật khẩu IdP thường dùng có thời hạn dài (năm). Service token thiết kế cho rotate thường xuyên hơn.
Có cách lách được không?
- OAuth client credentials với luồng headless: không chạy trực tiếp với Cloudflare Access: luồng OIDC của Cloudflare Access là authorization_code, không phải client_credentials.
- Tài khoản robot với mật khẩu + token lưu trữ: khả thi về kỹ thuật nhưng anti-pattern: tài khoản robot thành identity đặc quyền, chính sách khó viết.
Câu trả lời đúng: service token hoặc mTLS.
Service token vs mTLS: khi nào chọn cái nào
| Yếu tố | Service token | mTLS |
|---|---|---|
| Độ phức tạp thiết lập | Thấp, click tạo, copy header | Cao, cần root CA, pipeline cert client |
| Loại secret | Chuỗi (ID + secret) | Cert X.509 + private key |
| Công sức rotate | Tạo token mới, cập nhật cấu hình | Cấp lại cert, phân phối, thu hồi cũ |
| Định danh theo từng client | Có (mỗi client một token) | Có (mỗi cert một định danh) |
| Bằng chứng định danh phần cứng | Không | Có, cert có thể bind TPM/HSM |
| Phụ thuộc PKI | Không | Cần (root CA, intermediate, pipeline) |
| Mức độ chi tiết kiểm toán | Token ID trong log | Subject của cert trong log |
| Phù hợp khi | CI/CD, tích hợp nhanh, SDK | Đội thiết bị IoT, thiết bị có PKI, compliance bắt buộc |
Nguyên tắc:
- Bắt đầu bằng service token. Đơn giản, đủ dùng cho 80% trường hợp sử dụng.
- Chuyển sang mTLS khi:
- Tổ chức có PKI sẵn.
- Quy định yêu cầu định danh thiết bị bằng mật mã (tài chính, y tế).
- Quy mô hàng ngàn thiết bị (rotate service token thủ công không mở rộng được).
- Cần bind định danh với phần cứng (TPM).
Thiết lập 1: Service token
Bước 1: Tạo token
Zero Trust dashboard → Access → Service Auth → Service Tokens → Create Service Token.
- Name:
ci-deploy-prod(đặt tên mô tả trường hợp sử dụng: pattern<team>-<purpose>-<env>) - Duration: mặc định 1 năm. Đặt ngắn hơn cho trường hợp sử dụng rủi ro.
Lưu. Cloudflare hiện Client ID và Client Secret một lần duy nhất. Copy cả hai vào secret manager ngay.
- Định dạng Client ID:
abc123.access(tiền tố đọc được) - Định dạng Client Secret: chuỗi dài kiểu base64, không hiện lại được nếu bỏ lỡ.
Bước 2: Thêm vào chính sách Access
Chỉnh sửa Access application (ví dụ api.example.com) → Thêm chính sách:
- Name:
CI deployment - Action:
Service Auth(đây là điểm quan trọng: không phải Allow thường) - Include:
Service Token→ chọnci-deploy-prodvừa tạo.
Lưu. Không có Require block, service token không đi qua kiểm tra posture.
Bước 3: Sử dụng từ client
curl:
curl https://api.example.com/deploy \
-H "CF-Access-Client-Id: abc123.access" \
-H "CF-Access-Client-Secret: s3cr3t_long_string_here" \
-X POST -d '{"version":"v1.2.3"}'
GitHub Actions:
- name: Trigger deployment
env:
CF_ACCESS_CLIENT_ID: ${{ secrets.CF_ACCESS_CLIENT_ID }}
CF_ACCESS_CLIENT_SECRET: ${{ secrets.CF_ACCESS_CLIENT_SECRET }}
run: |
curl https://api.example.com/deploy \
-H "CF-Access-Client-Id: $CF_ACCESS_CLIENT_ID" \
-H "CF-Access-Client-Secret: $CF_ACCESS_CLIENT_SECRET" \
-X POST -d '{"version":"${{ github.sha }}"}'
Terraform (nếu Cloudflare provider gọi Access-protected endpoint):
provider "cloudflare" {
api_token = var.cf_api_token
}
data "http" "deploy" {
url = "https://api.example.com/deploy"
request_headers = {
"CF-Access-Client-Id" = var.cf_access_client_id
"CF-Access-Client-Secret" = var.cf_access_client_secret
}
}
Python (requests):
import requests
resp = requests.post(
"https://api.example.com/deploy",
headers={
"CF-Access-Client-Id": os.environ["CF_ACCESS_CLIENT_ID"],
"CF-Access-Client-Secret": os.environ["CF_ACCESS_CLIENT_SECRET"],
},
json={"version": "v1.2.3"},
timeout=10,
)
resp.raise_for_status()
Bước 4: Xác minh trong log
Zero Trust → Logs → Access → lọc theo Application api.example.com. Sự kiện từ service token hiển thị:
- User:
ci-deploy-prod.access(tên token làm định danh) - Connection method:
Service Token - Policy matched:
CI deployment
Nếu sự kiện không hiện → header sai, secret sai, hoặc chính sách không include token.
Luồng service token dạng hình
Thiết lập 2: mTLS
mTLS phức tạp hơn. Nhưng khi đã có PKI, nó mạnh hơn rõ rệt.
Bước 1: Chuẩn bị root CA
Bạn cần một root CA, có thể:
- CA tổ chức có sẵn (AD CS, HashiCorp Vault PKI, Smallstep, AWS Private CA).
- Tự tạo CA kiểm thử:
openssl req -x509 -sha256 -days 3650 -newkey rsa:4096 -keyout ca.key -out ca.crt.
Yêu cầu:
- Cert dạng PEM.
- Có private key tương ứng để ký cert client (private key không upload lên Cloudflare).
Bước 2: Upload root CA vào Cloudflare
Zero Trust → Settings → Authentication → Mutual TLS authentication → Add mTLS certificate.
- Paste nội dung
ca.crt(bao gồm các dòng-----BEGIN CERTIFICATE-----). - Name:
Corporate Device CA - Associated hostnames:
api.example.com(và các hostname khác muốn bật mTLS).
Lưu. Cloudflare xác minh định dạng cert → trạng thái Active.
Bước 3: Cấp cert client
Từ CA, cấp cert cho client:
# Tạo CSR từ client
openssl req -newkey rsa:2048 -keyout client.key -out client.csr \
-subj "/CN=github-actions-bot/O=Example Corp"
# CA sign CSR thành cert
openssl x509 -req -in client.csr \
-CA ca.crt -CAkey ca.key -CAcreateserial \
-out client.crt -days 365 -sha256
Kết quả: client.crt (cert công khai) + client.key (private key, giữ bí mật).
Bước 4: Khớp chính sách theo thuộc tính cert
Tạo chính sách Access application:
- Name:
mTLS from Corporate CA - Action:
Non-Identity(đây là action cho mTLS, không phải Allow thường). - Include:
Common Name→github-actions-bot(hoặc khớp pattern, ví dụ kết thúc bằng.corp.example.com).
Lưu.
Khớp có thể theo:
- Common Name (CN): tên trong
subject. - Issuer: ai ký cert (nếu nhiều CA).
- Country / Organization: các thuộc tính khác trong subject.
Bước 5: Client sử dụng cert
curl:
curl https://api.example.com/deploy \
--cert client.crt \
--key client.key \
-X POST -d '{"version":"v1.2.3"}'
Python:
resp = requests.post(
"https://api.example.com/deploy",
cert=("client.crt", "client.key"),
json={"version": "v1.2.3"},
timeout=10,
)
Go:
cert, _ := tls.LoadX509KeyPair("client.crt", "client.key")
client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
Certificates: []tls.Certificate{cert},
},
},
}
resp, _ := client.Post("https://api.example.com/deploy", ...)
Luồng bắt tay mTLS
Rotate: pattern cửa sổ chồng lấn
Cả service token và cert mTLS đều cần rotate định kỳ. Rotate sai = gián đoạn.
Rotate service token
Quy trình đúng:
- T0, Tạo token mới. Dashboard → Create new token
ci-deploy-prod-v2. - T0, Thêm token mới vào cùng chính sách. Chính sách
CI deploymentgiờ include cả cũ và mới. - T0 → T+1h, Cập nhật client secret trong secret manager (GitHub secrets, Vault, v.v.). Triển khai theo đợt, không cập nhật tất cả CI pipeline cùng lúc.
- T+1h, Xác minh token mới đang được dùng. Kiểm tra log, sự kiện có tên
ci-deploy-prod-v2. - T+1h, Thu hồi token cũ. Dashboard → token cũ → Delete.
Cửa sổ chồng lấn (T0 → T+1h) là lúc cả hai token cùng hợp lệ. Nếu bạn thu hồi cũ trước khi chuyển xong, sẽ gián đoạn.
Rotate cert mTLS
Tương tự, nhưng qua CA:
- T0, Cấp cert mới với CN trùng (hoặc CN khác, tùy cách khớp chính sách).
- T0, Đảm bảo chuỗi tin cậy CA chưa đổi (nếu đổi Intermediate CA cùng lúc, upload mới vào Cloudflare trước).
- T0 → T+1h, Chuyển file cert client ở CI/thiết bị.
- T+24h, Thu hồi cert cũ trong CA (CRL hoặc OCSP).
Lưu ý: Cloudflare không kiểm tra CRL cho cert client mTLS native, nếu cần áp dụng thu hồi, bạn phải gỡ cert ID khỏi chính sách một cách tường minh, hoặc cập nhật pattern khớp (CN khác).
Tần suất rotate khuyến nghị
| Loại client | Service token | mTLS cert |
|---|---|---|
| CI job ngắn hạn | 90 ngày | N/A (thường dùng service token) |
| Dịch vụ chạy dài | 180 ngày | 1 năm |
| Fleet thiết bị | N/A | 1-2 năm |
| Bảo mật cao / compliance | 30 ngày | 90 ngày |
Chính sách cho non-human: các pattern
Pattern 1: Endpoint dịch vụ chỉ cho CI
# Access application: api.example.com
policies:
- name: "CI deployment"
action: service_auth
include:
- service_token: "ci-deploy-prod"
- service_token: "ci-deploy-staging"
Chỉ token CI vào. Không có người dùng, không truy cập qua browser.
Pattern 2: Endpoint cho cả người dùng và bot
# Access application: api.example.com
policies:
# Policy 1: human users (order 1)
- name: "Developers manual"
action: allow
include:
- groups: [Engineering]
require:
- device_posture: [managed_device]
# Policy 2: CI service tokens (order 2)
- name: "CI auto-deploy"
action: service_auth
include:
- service_token: "ci-deploy-prod"
Endpoint ứng dụng cho phép cả developer là con người (qua browser + IdP) lẫn CI (qua service token). Một endpoint, hai đường xác thực.
Pattern 3: mTLS cho đội thiết bị
# Access application: telemetry.example.com
policies:
- name: "Factory devices"
action: non_identity
include:
- common_name: "^device-[a-z0-9-]+\\.factory\\.corp$"
Khớp pattern CN để cho phép hàng ngàn thiết bị khác nhau mà không cần tạo từng entry.
Log kiểm toán cho non-human
Zero Trust → Logs → Access → lọc:
- Connection method:
Service Token/Non-Identity mTLS - User: tên token hoặc CN cert
Mỗi sự kiện có:
- Timestamp
- Application
- Chính sách đã khớp
- Tên token hoặc CN cert
- IP nguồn
- Quốc gia
- User agent
Đẩy log sang SIEM
Log được đẩy về SIEM qua Logpush (Part 17 sẽ bàn). Dataset: access_requests. Trường quan trọng cho non-human:
{
"app_name": "api.example.com",
"allowed": true,
"service_token_id": "abc123.access",
"service_token_name": "ci-deploy-prod",
"connection_method": "service_token",
"created_at": "2026-05-07T10:24:33Z",
"ip_address": "203.0.113.42",
"country": "SG"
}
Phát hiện bất thường
Pattern sự kiện đáng chú ý:
- Service token gọi từ quốc gia lạ: CI thường gọi từ IP của nhà cung cấp CI. Request từ quốc gia khác = rò rỉ credential.
- Tần suất tăng đột biến: đường nền 100 req/giờ, đột biến 10,000 = lạm dụng hoặc script chạy lạc.
- Xác thực thất bại sau khi rotate: token cũ vẫn đang thử = client chưa chuyển xong.
Đánh đổi
| Quyết định | Option A | Option B | Khuyến nghị |
|---|---|---|---|
| Loại xác thực | Service token | mTLS | Token cho đa số. mTLS khi có PKI hoặc compliance |
| Phạm vi token | 1 token cho nhiều chính sách | 1 token mỗi trường hợp sử dụng | 1 theo từng trường hợp sử dụng, kiểm toán + thu hồi chi tiết |
| Lưu trữ secret | GitHub secrets / Vault | Biến môi trường plaintext | Luôn dùng secret manager. Không bao giờ plaintext |
| Nhịp rotate | Theo chính sách (90/180 ngày) | Thủ công khi có vấn đề | Tự động, đặt lịch, tạo runbook rotate |
| Hết hạn | Token không hết hạn | Token hết hạn 1 năm | Có hết hạn, ép buộc nhịp rotate |
| Action của chính sách | Service Auth | Allow (bypass Access) | Service Auth, giữ dấu vết kiểm toán, không bypass |
Các anti-pattern phổ biến
1. “Hardcode secret vào source code”
# BAD
CF_SECRET = "s3cr3t_long_string_here"
Secret rò rỉ vào git history. Rotate khó, cần build lại container. Dùng secret manager.
2. “Dùng một service token cho tất cả CI job”
Một token → một định danh trong log → không phân biệt được job nào vừa triển khai. Dấu vết kiểm toán vô dụng. Tạo token riêng cho từng pipeline quan trọng.
3. “Bypass Access cho endpoint API”
policies:
- name: "API public"
action: bypass # anti-pattern
Bypass = traffic không qua kiểm tra Access, cũng không có log. Thay vì bypass, dùng Service Auth với token. Log + chính sách vẫn hoạt động.
4. “Để private key của root CA mTLS trên CI runner”
Private key của root CA phải ở HSM hoặc offline. Chỉ intermediate CA mới được để trên runner/vault. Nếu key root CA rò rỉ → toàn bộ chuỗi tin cậy vỡ, mọi cert client phải cấp lại.
5. “Không đặt hết hạn cho service token”
Token không hết hạn = secret tồn tại vĩnh viễn. Nếu nhà cung cấp CI rò rỉ (đã xảy ra với GitHub, CircleCI), kẻ tấn công dùng được cho đến khi bạn phát hiện. Đặt hết hạn tối đa 1 năm.
6. “Rotate bằng cách tắt cũ trước, tạo mới sau”
Cửa sổ gián đoạn. Luôn tạo mới → chuyển xong → thu hồi cũ.
Danh sách kiểm tra: trước khi đưa non-human auth vào production
Thiết lập:
- Tên token/cert theo quy ước đặt tên (
<team>-<purpose>-<env>). - Secret lưu trong secret manager, không hardcode.
- Action chính sách đúng:
Service Authcho token,Non-Identitycho mTLS. - Session duration / hiệu lực cert đặt tường minh, không để mặc định.
Rotate:
- Runbook rotate đã tài liệu hóa (khi nào, ai làm, bước nào).
- Nhắc lịch cho ngày rotate.
- Quy trình cửa sổ chồng lấn đã kiểm thử ít nhất 1 lần trong staging.
Kiểm toán:
- Log đẩy về SIEM.
- Cảnh báo thiết lập cho: token từ quốc gia lạ, xác thực thất bại tăng đột biến, cert hết hạn.
- Dashboard theo dõi khối lượng traffic non-human.
Riêng mTLS:
- Private key root CA offline hoặc trong HSM.
- Intermediate CA được dùng để cấp cert client.
- Xử lý thu hồi (cập nhật chính sách thủ công) đã tài liệu hóa.
Bài học thực tế
- CI pipeline gián đoạn vì rotate sai: thu hồi cũ trước khi chuyển xong. Lỗi kinh điển, dễ tránh nếu theo pattern chồng lấn.
- Service token rò rỉ qua commit GitHub: hardcode vào source để “kiểm thử nhanh”, quên thu hồi. Sau 2 tuần, script kid dùng token cào API nội bộ. Bài học: quét secret trong CI + pre-commit hook.
- Cert mTLS hết hạn trong production lúc 2h sáng: không có giám sát hạn. Một sáng API gọi về thất bại 100%. Giám sát hạn cert = cảnh báo ưu tiên cao.
- Tên token
token-1,token-2,temp-token: kiểm toán vô dụng. Quy ước đặt tên là khoản đầu tư rẻ lãi nhiều. - Đừng viết một chính sách “allow any service token”. Mỗi token phải có chính sách riêng hoặc danh sách tường minh. Wildcard = không biết ai đang truy cập gì.
Kết luận
Xác thực non-human là phần thường bị coi nhẹ trong triển khai Zero Trust. Tập trung vào luồng con người vì nhiều hơn và dễ nhìn hơn, nhưng tài khoản bot là nơi kẻ tấn công thường nhắm vì:
- Ít giám sát.
- Rotate thường xuyên bị bỏ qua.
- Secret rò rỉ qua lịch sử CI/CD.
Service token + mTLS của Cloudflare Access giải quyết đúng bài toán. Nhưng chúng chỉ hoạt động tốt khi:
- Quy ước đặt tên nhất quán.
- Lịch rotate tự động (không dựa vào trí nhớ).
- Log kiểm toán đẩy SIEM + cảnh báo bất thường.
- Lưu trữ secret qua secret manager, không hardcode.
Nếu phải nhớ một câu:
Service token cho CI dễ thiết lập 5 phút. Nhưng vận hành đúng, đặt tên, rotate, kiểm toán, không hardcode, mất 1 tuần. Cắt góc ở 1 tuần đó là chỗ lỗi production nằm chờ.
Trong Part 7 mình sẽ bàn SCIM + group sync, giải quyết triệt để vấn đề claim lỗi thời từ Part 5, đồng thời làm off-boarding thời gian thực.
Tài liệu tham khảo
- Cloudflare Access, Service tokens
- Cloudflare Access, mTLS
- Logpush dataset: access_requests
- OpenSSL x509 docs
Trong series này:
- ← Part 5: Integrate IdP
- Tiếp theo → Part 7: SCIM và group sync
- Xem toàn bộ: Series Cloudflare One Handbook