Skip to content

Truy Cập Dịch Vụ & Application Default Credentials Patterns

Why this matters in production

Năm file trước xây dựng toàn bộ cơ chế: cluster ký token, metadata server đổi token, IAM cấp quyền. File này trả lời câu hỏi mà developer thực sự gặp hằng ngày: trong code, tôi gọi Secret Manager / Cloud Storage / Pub/Sub như thế nào để cơ chế đó hoạt động? Câu trả lời ngắn gọn — Application Default Credentials (ADC) — và câu trả lời dài là hiểu vì sao ADC làm Workload Identity "vô hình" với code, cùng các pattern truy cập dịch vụ đúng đắn không-key.

Tầm quan trọng production nằm ở chỗ đây là nơi lý thuyết bảo mật gặp thực tế viết code, và là nơi các sai lầm "nhỏ" gây hậu quả lớn:

  • Code chứa đường dẫn key file là bằng chứng của anti-pattern. Khi bạn thấy GOOGLE_APPLICATION_CREDENTIALS=/secrets/key.json trong một Deployment GKE, đó là dấu hiệu chắc chắn workload đang dùng SA key tĩnh thay vì Workload Identity — đúng vector mà cả chương này nhằm loại bỏ. Hiểu ADC giúp nhận ra và sửa pattern này.

  • Secret Manager là điểm khởi đầu của nhiều chuỗi tin cậy. Workload thường đọc database password, API key bên thứ ba từ Secret Manager lúc khởi động. Nếu việc đọc Secret Manager đó lại cần một SA key, bạn có vấn đề "con gà-quả trứng" về credential. Workload Identity cắt nút thắt này: workload xác thực với Secret Manager bằng danh tính KSA, không cần secret nào để lấy secret.

  • Artifact Registry pull image cũng cần xác thực. Không chỉ runtime — ngay cả việc kéo image từ Artifact Registry riêng tư cần credential. Credential helper qua Workload Identity giải quyết điều này mà không nhúng key vào node hay image.

  • Khác biệt local-vs-cluster gây nhầm lẫn kinh điển. "Chạy trên máy tôi thì được, lên cluster thì 403" (hoặc ngược lại) gần như luôn là vấn đề ADC dò credential khác nhau giữa hai môi trường. Hiểu thứ tự ADC giải thích ngay lớp lỗi này.

Internal model: Application Default Credentials

ADC là gì và dò credential theo thứ tự nào

Application Default Credentials là chiến lược mà các Google Cloud client library dùng để tự động tìm credential mà không cần code chỉ định tường minh. Khi bạn viết storage.NewClient(ctx) (Go) hay storage.Client() (Python) mà không truyền credential, library chạy quy trình ADC, dò theo thứ tự:

  1. Biến môi trường GOOGLE_APPLICATION_CREDENTIALS: nếu được đặt, trỏ tới một file credential (SA key JSON hoặc cấu hình federation). Đây là cơ chế cho SA key tĩnh — và là cái ta muốn không dùng trên GKE.
  2. Credential của gcloud CLI: credential từ gcloud auth application-default login, dùng cho phát triển local.
  3. Metadata server của môi trường: trên GKE/Compute Engine, library hỏi metadata endpoint …/instance/service-accounts/default/token. Đây chính là điểm gke-metadata-server chặn (file 03), kích hoạt token exchange Workload Identity.

Trên một Pod GKE đã cấu hình đúng Workload Identity và không đặt GOOGLE_APPLICATION_CREDENTIALS, ADC rơi xuống bước 3, gọi metadata server, và nhận federated token — toàn bộ trong suốt. Đây là lý do tài liệu khẳng định code dùng client library "should work without modification": ADC bước 3 Workload Identity.

Thứ tự dò đầy đủ và "well-known file"

Mô tả ba bước ở trên là rút gọn; thứ tự dò chính tắc mà mọi Google Cloud client library tuân theo (qua thư viện google-auth chung) chi tiết hơn, và biết đủ các bước giúp giải thích các hành vi "lạ":

  1. GOOGLE_APPLICATION_CREDENTIALS — trỏ tới một file credential. File này có thể là service account key ("type": "service_account"), external account / federation config ("type": "external_account"), impersonated service account ("type": "impersonated_service_account"), hoặc authorized user ("type": "authorized_user"). Chính vì ADC phân loại theo trường type, biến môi trường này không nhất thiết là key tĩnh — nhưng trên GKE, mọi giá trị của nó đều thừa và gây hại (xem dưới).
  2. Well-known file của gcloud — file application_default_credentials.json tại ~/.config/gcloud/ (Linux/macOS) hoặc %APPDATA%\gcloud\ (Windows), sinh ra bởi gcloud auth application-default login. Đây là cơ chế ADC cho phát triển local, và là lý do "máy tôi chạy được": local dùng credential người dùng, cluster dùng metadata server — hai nguồn ADC khác nhau hoàn toàn.
  3. Metadata server của môi trường (GKE/GCE/Cloud Run): library gọi …/instance/service-accounts/default/token với header Metadata-Flavor: Google. Trên GKE đây chính là điểm gke-metadata-server chặn (file 03).

Một workload GKE đúng chuẩn không có bước 1 và bước 2 khả dụng (không biến môi trường, không file gcloud trong image), nên ADC luôn rơi xuống bước 3 một cách tất định. Đây là trạng thái cần đạt: ADC không "may rủi" chọn nguồn — nó chỉ còn một nguồn để chọn.

Quota project và universe domain — hai chi tiết hay gây 403 khó hiểu

Federated token từ Workload Identity, theo mặc định, không gắn sẵn một billing/quota project cho các API dùng quota theo project gọi (như Service Usage, một số API generative). Hệ quả: một lời gọi xác thực thành công vẫn có thể trả 403 kèm thông điệp kiểu "... requires a quota project ... " nếu API đó bắt buộc quota project mà ADC không suy ra được. Đây là lý do annotation iam.gke.io/credential-quota-project (file 04) tồn tại — nó cấp quota project ở tầng metadata server. Ở tầng code, một số library cho phép đặt quota project tường minh (QuotaProjectID), nhưng cách sạch nhất trên GKE là để annotation lo. Tương tự, universe_domain (mặc định googleapis.com) chỉ khác googleapis.com trong môi trường Sovereign Cloud/Trusted Partner Cloud — nếu một workload bỗng không xác thực được sau khi đổi universe, đây là nghi phạm.

Vì sao Workload Identity "vô hình" với code

Điểm thanh lịch của thiết kế: bước 3 của ADC trên GKE giống hệt bước 3 trên một VM Compute Engine — cùng endpoint metadata, cùng giao thức. Client library không biết và không cần biết nó đang ở Workload Identity hay trên VM thường. gke-metadata-server lo toàn bộ khác biệt ở tầng node (file 03). Hệ quả: migration từ SA key sang Workload Identity thường không đụng một dòng code — chỉ cần (a) bỏ GOOGLE_APPLICATION_CREDENTIALS và việc mount key, (b) đặt serviceAccountName đúng KSA, (c) cấp IAM binding cho KSA. Code gọi storage.NewClient(ctx) y nguyên.

Anti-pattern hiển thị: GOOGLE_APPLICATION_CREDENTIALS

Ngược lại, sự hiện diện của GOOGLE_APPLICATION_CREDENTIALS trỏ tới một key file là "code smell" rõ ràng nhất của việc không dùng Workload Identity. Vì ADC ưu tiên bước 1 trước bước 3, một biến môi trường này vô hiệu hóa Workload Identity ngay cả khi cluster đã bật nó — library dùng key tĩnh thay vì gọi metadata server. Khi audit một cluster, tìm biến này (và việc mount Secret chứa key) là cách nhanh phát hiện workload còn dùng key.

Client library xử lý refresh và cache token thế nào

Một sự hiểu lầm phổ biến là nghĩ ADC "lấy token một lần". Thực tế, đối tượng credential mà ADC trả về là một token source có khả năng tự làm mới: nó giữ access token hiện tại cùng thời điểm hết hạn, và trước mỗi lời gọi RPC, transport layer kiểm tra token còn hạn không. Khi token sắp hết hạn (Cloud Client Libraries refresh chủ động khi còn dưới ~3 phút 45 giây — file 03), library tự gọi lại metadata server để lấy token mới trước khi token cũ chết, nên workload không bao giờ thấy một 401 do hết hạn giữa chừng. Hệ quả thực hành: không bao giờ tự gọi print-access-token rồi nhét vào biến môi trường/header thủ công — bạn sẽ tạo ra đúng cái token tĩnh-trong-một-giờ mà cơ chế refresh sinh ra để tránh. Hãy luôn truyền đối tượng credential (hoặc để client tự khởi tạo qua ADC), không truyền chuỗi token.

Khác biệt nhỏ giữa các ngôn ngữ

Cơ chế ADC nhất quán xuyên ngôn ngữ, nhưng vài chi tiết khởi tạo khác nhau đáng lưu ý khi debug:

  • Go: google.FindDefaultCredentials(ctx, scopes...) hoặc đơn giản tạo client storage.NewClient(ctx); scope thường được client suy ra. Lỗi điển hình khi quên scope chỉ xuất hiện với một số API cũ.
  • Python: google.auth.default() trả về (credentials, project_id) — chú ý nó cũng trả về project được suy ra; nếu project_idNone trên GKE, đó là tín hiệu metadata server không cấp project, liên quan vấn đề quota project ở trên.
  • Java: GoogleCredentials.getApplicationDefault(); trên GKE thường cần .createScoped(...) cho một số API.
  • Node.js: new GoogleAuth() rồi getClient(); thư viện tự dò cùng thứ tự.

Điểm chung: trong cả bốn, không nơi nào code chỉ định credential tường minh — đó chính là dấu hiệu code đang dùng ADC đúng cách và do đó portable giữa local và cluster.

Production architecture patterns

Pattern: Secret Manager qua Workload Identity (không key để lấy secret)

Tình huống: workload cần một database password lúc khởi động. Cách sai: mount SA key để gọi Secret Manager. Cách đúng:

  1. KSA của workload được cấp roles/secretmanager.secretAccessor trên đúng secret cần (không phải toàn project), qua principal binding (file 04):
bash
gcloud secrets add-iam-policy-binding db-password \
  --role="roles/secretmanager.secretAccessor" \
  --member="principal://iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/PROJECT_ID.svc.id.goog/subject/ns/NAMESPACE/sa/KSA_NAME"
  1. Code đọc secret qua client library, ADC tự xác thực bằng danh tính KSA:
go
client, _ := secretmanager.NewClient(ctx)   // ADC → metadata server → WI
result, _ := client.AccessSecretVersion(ctx, &secretmanagerpb.AccessSecretVersionRequest{
    Name: "projects/PROJECT_ID/secrets/db-password/versions/latest",
})
password := string(result.Payload.Data)

Không có secret nào để lấy secret; chuỗi tin cậy gốc là danh tính KSA. Đây là cách phá vỡ vấn đề bootstrap credential. Lưu ý cấp quyền trên từng secret, không trên toàn project — granularity per-secret là điều kiện để một workload bị chiếm chỉ lộ đúng các secret nó cần.

Pattern: tích hợp Secret Manager qua CSI driver (tùy chọn)

Một biến thể: dùng Secret Manager CSI driver để mount secret như file vào Pod, với driver tự xác thực bằng Workload Identity. Workload đọc secret như file thường, không cần client library. Đánh đổi: thêm một thành phần (CSI driver) nhưng tách logic đọc secret khỏi code. Cả hai cách (client library trực tiếp vs CSI) đều xác thực qua Workload Identity — chọn theo việc bạn muốn secret ở dạng API call hay file mount.

Pattern: Cloud Storage / Pub/Sub / BigQuery với KSA-per-workload

Nguyên tắc nhất quán: mỗi workload một KSA, mỗi KSA quyền tối thiểu trên đúng resource:

  • Service xử lý ảnh: KSA image-processorroles/storage.objectAdmin trên đúng một bucket ảnh.
  • Service publish event: KSA event-publisherroles/pubsub.publisher trên đúng các topic nó dùng.
  • Service analytics: KSA analyticsroles/bigquery.dataViewer trên đúng dataset cần.

Code mọi service đều chỉ gọi client library tương ứng; ADC lo xác thực. Khác biệt giữa các service nằm hoàn toàn ở IAM binding của KSA, không ở code và không ở credential. Đây là điểm đẹp nhất của mô hình: bảo mật trở thành cấu hình IAM khai báo, tách khỏi logic ứng dụng.

Pattern: credential helper cho Artifact Registry

Kéo image từ Artifact Registry riêng tư cần xác thực. Trên GKE, node service account thường được cấp roles/artifactregistry.reader để kubelet pull image — đây là xác thực ở tầng node, không phải workload, và là cách chuẩn cho image pull. Trong container (ví dụ một Pod build/push image, hoặc tool gọi registry), dùng credential helper docker-credential-gcr cấu hình để lấy token qua ADC/Workload Identity, thay vì nhúng key:

bash
# Trong image: cấu hình docker dùng credential helper của gcloud
gcloud auth configure-docker REGION-docker.pkg.dev
# helper sẽ lấy token qua ADC (metadata server → WI) khi push/pull

Nguyên tắc: ở mọi tầng cần credential Google — runtime API, image pull, registry push — luôn có một đường lấy token qua ADC/Workload Identity, không có đường nào cần key tĩnh.

Pattern: gọi service ngoài Google bằng danh tính federated

Khi workload GKE cần gọi một dịch vụ ngoài Google mà dịch vụ đó tin một danh tính external (ví dụ một API verify OIDC token, hoặc một tài nguyên AWS), có thể đảo chiều federation: workload dùng danh tính Google (qua impersonation chain hoặc projected token audience tùy chỉnh — file 02) để chứng minh danh tính với bên kia. Chuỗi impersonation (roles/iam.serviceAccountTokenCreator) cho phép một danh tính tạo token cho một danh tính khác trong một chuỗi có kiểm soát. Đây là kịch bản nâng cao, nhưng nguyên tắc giống hệt: token ngắn hạn, có audience, thay cho key.

Common mistakes / anti-patterns

1. Đặt GOOGLE_APPLICATION_CREDENTIALS trên Pod GKE. Mount SA key và trỏ biến môi trường vào nó. Vì sao xảy ra: copy cấu hình từ môi trường non-GKE, hoặc tutorial cũ. Hệ quả: vô hiệu hóa Workload Identity (ADC ưu tiên key), key tĩnh trong cluster. Phòng tránh: không đặt biến này trên GKE; để ADC rơi xuống metadata server; audit tìm và xóa biến này.

2. Cấp quyền Secret Manager/Storage ở cấp project thay vì resource. Cho KSA roles/secretmanager.secretAccessor trên cả project. Vì sao xảy ra: tiện, "cho đọc mọi secret". Hệ quả: một workload bị chiếm đọc mọi secret của project, không chỉ secret của nó. Phòng tránh: cấp quyền trên đúng từng secret/bucket/topic; granularity per-resource.

3. Đọc secret/token một lần rồi cache vĩnh viễn trong code. Lấy giá trị secret hoặc token lúc khởi động, dùng mãi. Vì sao xảy ra: tối ưu sai chỗ. Hệ quả: secret xoay vòng (rotate) không được phản ánh; token hết hạn gây 401 sau ~1 giờ. Phòng tránh: để client library quản token; đọc lại secret theo chu kỳ nếu secret có rotation.

4. Dùng node service account cho quyền workload. Cấp quyền workload cần vào node SA thay vì KSA. Vì sao xảy ra: "cho nhanh", hoặc nhầm tầng. Hệ quả: mọi Pod trên node có quyền đó (kể cả Pod không liên quan), và Pod hostNetwork/bị chiếm lấy được — phá vỡ granularity. Phòng tránh: node SA chỉ giữ quyền hạ tầng tối thiểu (logging, monitoring, image pull); quyền workload luôn qua KSA + Workload Identity.

5. Test local thất bại rồi nhúng key để "cho chạy". Local không có metadata server nên ADC bước 3 thất bại; developer nhúng key cho chạy rồi lỡ commit/đẩy lên. Vì sao xảy ra: không biết local dùng gcloud auth application-default login. Hệ quả: key tĩnh lan ra. Phòng tránh: local dùng gcloud auth application-default login (ADC bước 2) hoặc impersonation; không nhúng key.

GCP-native implementation guidance

bash
# Local development: cấp credential cho ADC mà KHÔNG cần key file
gcloud auth application-default login
# ADC bước 2 sẽ dùng credential này; code y hệt như trên cluster

# Local nâng cao: impersonate một GSA để test với đúng quyền workload (không key)
gcloud auth application-default login \
  --impersonate-service-account=GSA@PROJECT_ID.iam.gserviceaccount.com

# Kiểm tra ADC đang resolve thành danh tính nào (debug local-vs-cluster)
gcloud auth application-default print-access-token >/dev/null && echo "ADC OK"

Trong Deployment GKE, cấu hình tối thiểu để workload dùng Workload Identity — chú ý không có key, khôngGOOGLE_APPLICATION_CREDENTIALS:

yaml
apiVersion: apps/v1
kind: Deployment
spec:
  template:
    spec:
      serviceAccountName: image-processor   # KSA đã được cấp IAM binding
      containers:
      - name: app
        image: REGION-docker.pkg.dev/PROJECT/repo/app:tag
        # KHÔNG mount key, KHÔNG đặt GOOGLE_APPLICATION_CREDENTIALS

Cấu hình này, kết hợp IAM binding cho principal://...sa/image-processor (file 04) và cluster/node pool ở GKE_METADATA (file 03), là toàn bộ những gì cần để workload gọi Google API không-key.

Để biến "không key" từ best-practice thành bất biến enforce được, kết hợp ba lớp: (1) Org Policy iam.disableServiceAccountKeyCreation ở cấp tổ chức để chặn tạo SA key ngay từ gốc (Chương 12, file 04); (2) một admission policy chặn Pod có biến GOOGLE_APPLICATION_CREDENTIALS hoặc mount Secret kiểu key vào đường dẫn credential (Gatekeeper/Policy Controller); (3) một check CI quét manifest tìm cùng các pattern đó trước khi merge. Ba lớp này khóa anti-pattern ở ba thời điểm khác nhau — tạo credential, admission, và review — nên một sai sót ở một lớp vẫn bị lớp khác bắt. Khi cả ba được áp, việc một workload "lỡ" dùng key trở thành điều không thể, không chỉ là điều không nên.

Operational implications

Pattern ADC + Workload Identity thay đổi căn bản mối quan hệ giữa code và credential. Trong mô hình cũ, credential là một đầu vào của ứng dụng — phải cung cấp, mount, truyền, rotate. Trong mô hình mới, credential là một thuộc tính của môi trường chạy — ứng dụng chỉ tuyên bố "tôi là KSA này" (qua serviceAccountName), và môi trường (metadata server + IAM) lo phần còn lại. Hệ quả là code trở nên portable và sạch hơn: cùng một binary chạy local (ADC dùng gcloud), trên GKE (ADC dùng metadata server/WI), trên Compute Engine (ADC dùng VM SA) mà không đổi một dòng — chỉ môi trường cung cấp danh tính khác nhau.

Đối với đội ngũ vận hành, điều này dịch chuyển trọng tâm review từ "credential có được bảo vệ đúng không" sang "IAM binding của KSA có tối thiểu không". Câu hỏi audit trở nên đơn giản và khai báo: với mỗi workload, đọc serviceAccountName, tra IAM binding của principal đó, xác nhận quyền khớp nhu cầu. Không còn phải truy vết "key này ở đâu, ai có bản sao". Đây là lý do sâu xa khiến tài liệu Google gọi Workload Identity là phương thức manageable — không chỉ an toàn hơn, mà còn dễ suy luận và dễ audit hơn về bản chất.

File 07 đóng lại chương bằng năng lực quan trọng nhất khi mọi thứ trên không hoạt động: quy trình debug có hệ thống để định vị token exchange thất bại ở tầng nào — projected token, metadata server, STS, hay IAM binding.

References