Skip to content

Workload Identity Federation cho External IdP — Liên Bang Danh Tính Đa Đám Mây

Why this matters in production

Cho đến giờ, "external identity" mà Google tin tưởng là KSA của một cluster GKE. Nhưng cơ chế federation không bị giới hạn ở GKE — nó là một khả năng tổng quát: bất kỳ identity provider nào phát hành token có thể verify được đều có thể được Google tin tưởng và đổi lấy credential Google short-lived. Đây là Workload Identity Federation "thuần" (không gắn GKE), và nó giải quyết một trong những nguồn rò rỉ credential lớn nhất trong thực tế: service account key nằm trong CI/CD secret và trong workload đa đám mây.

Tầm quan trọng production rất cụ thể và đo đếm được:

  • CI/CD là nơi SA key bị lộ nhiều nhất. Pipeline GitHub Actions, GitLab CI, Jenkins truyền thống lưu một SA JSON key trong secret để deploy lên GCP. Key đó nằm trong cấu hình CI, log lỗi (vô tình in ra), fork của repo, và lịch sử pipeline. Workload Identity Federation cho phép runner đổi OIDC token ngắn hạn của chính nó lấy quyền GCP — xóa sạch SA key khỏi CI. Đây là một trong những cải thiện bảo mật có ROI cao nhất mà một tổ chức có thể làm.

  • Multi-cloud không nên có credential chéo tĩnh. Workload chạy trên AWS (EKS, Lambda, EC2) hay Azure cần gọi Google API thường được cấp một SA key — một credential Google nằm trên hạ tầng đám mây khác. Federation cho phép workload dùng danh tính cloud sẵn có (AWS IAM role, Azure Entra ID) để đổi lấy quyền GCP, không có credential Google tĩnh nào rời khỏi Google.

  • Confused deputy là rủi ro thật của federation. Sức mạnh của federation — "tin tưởng token từ IdP bên ngoài" — cũng là điểm yếu nếu cấu hình lỏng. Nếu bạn tin token từ token.actions.githubusercontent.com mà không giới hạn repo nào, thì bất kỳ repo GitHub nào trên thế giới — kể cả của kẻ tấn công — cũng đổi được token lấy quyền GCP của bạn. Attribute condition là cơ chế ngăn điều này, và hiểu nó là bắt buộc, không tùy chọn.

Điểm mental model thống nhất toàn chương: federation cho external IdP dùng cùng STS, cùng RFC 8693 token exchange, cùng principal/principalSet như GKE Workload Identity. Khác biệt duy nhất là nguồn của JWTcách cấu hình niềm tin (pool + provider thay vì pool GKE cố định). Nắm vững GKE WI ở các file trước nghĩa là bạn đã hiểu 80% federation external.

Internal model: pool và provider cho external IdP

Workload Identity Pool (external) khác pool GKE thế nào

Với GKE, pool PROJECT_ID.svc.id.goog được tạo tự động và cố định. Với external IdP, bạn tự tạo pool, và một pool đại diện một môi trường danh tính bên ngoài. Tài liệu khuyến nghị một pool cho mỗi môi trường non-Google-Cloud (dev, staging, prod) để cô lập. Một pool có thể chứa nhiều provider.

Workload Identity Pool Provider

Đây là thành phần mới so với GKE. Một provider mô tả quan hệ tin cậy với một IdP cụ thể: nó khai báo IdP là ai (issuer URL với OIDC, account với AWS), token được verify thế nào, claim được ánh xạ ra sao (attribute mapping), và điều kiện chấp nhận (attribute condition). Các loại provider được hỗ trợ theo tài liệu:

  • AWS: tin tưởng AWS account, xác thực bằng caller identity (AWS SigV4-signed GetCallerIdentity).
  • Microsoft Entra ID (Azure AD): OIDC provider của Azure.
  • GitHub (Actions): OIDC issuer token.actions.githubusercontent.com.
  • GitLab: OIDC token của GitLab CI.
  • Kubernetes clusters: cluster Kubernetes bất kỳ (kể cả ngoài GKE) làm OIDC provider.
  • Okta, On-premises Active Directory Federation Services (AD FS): OIDC/SAML enterprise IdP.
  • OIDC genericSAML: bất kỳ IdP nào tuân OIDC hoặc SAML 2.0.
  • Terraform: cho luồng IaC.

Token exchange RFC 8693 qua STS

Luồng giống hệt khung file 03, chỉ thay nguồn JWT:

[1] Workload trên IdP bên ngoài lấy token của IdP đó
    (GitHub Actions OIDC JWT / AWS SigV4 / Azure token / ...)

[2] Workload gọi Security Token Service:
    POST https://sts.googleapis.com/v1/token
    grant_type=urn:ietf:params:oauth:grant-type:token-exchange  (RFC 8693)
    subject_token = token của IdP bên ngoài
    audience = //iam.googleapis.com/projects/.../workloadIdentityPools/POOL/providers/PROVIDER

[3] STS verify token theo cấu hình provider:
    - OIDC: tải JWKS của issuer, verify chữ ký, kiểm iss/aud/exp
    - AWS: verify SigV4 caller identity
    - áp attribute mapping → sinh google.subject + attribute.*
    - áp attribute condition → chấp nhận/từ chối

[4] STS trả "federated access token" đại diện danh tính external

[5a] (direct) federated token gọi thẳng Google API
[5b] (impersonation) federated token mạo danh một GSA → token GSA → gọi API

Tài liệu xác nhận hệ thống theo "the OAuth 2.0 token exchange specification" (RFC 8693), credential external gửi tới STS tại sts.googleapis.com, STS verify và trả federated token. Đây chính xác là khung file 03 — bằng chứng cho luận điểm "một cơ chế, nhiều nguồn".

subject_token_type: STS biết verify loại token nào

Điểm khác biệt cụ thể giữa các IdP nằm ở trường subject_token_type của lời gọi STS — nó cho STS biết subject_token thuộc dạng nào để verify đúng cách:

  • urn:ietf:params:oauth:token-type:jwt — token là một JWT (OIDC). Dùng cho GitHub Actions, GitLab, Azure Entra ID, OIDC generic, Kubernetes. STS verify bằng JWKS của issuer.
  • urn:ietf:params:oauth:token-type:id_token — biến thể ID token OIDC.
  • urn:ietf:params:aws:token-type:aws4_request — dùng cho AWS. Đây không phải JWT mà là một request GetCallerIdentity đã ký SigV4 (xem dưới).
  • urn:ietf:params:oauth:token-type:saml2 — assertion SAML 2.0 mã hóa base64, cho IdP SAML.

Hiểu trường này giải thích vì sao cấu hình provider khác nhau giữa OIDC và AWS: với OIDC bạn khai --issuer-uri (để STS tìm JWKS), với AWS bạn khai --account-id (để STS biết tin account nào).

Cơ chế AWS: GetCallerIdentity làm "token"

AWS không phát hành OIDC token cho mọi workload, nên federation với AWS dùng một thủ thuật tinh tế: subject token chính là một request sts:GetCallerIdentity đã được ký SigV4, đóng gói lại. Workload trên AWS (có sẵn AWS credential qua instance metadata/role) tạo một request đã ký gửi tới AWS STS, rồi không gửi nó cho AWS mà đưa nguyên request đã ký đó cho Google STS làm subject_token. Google STS "phát lại" (replay) request đã ký này tới AWS sts.amazonaws.com; AWS trả về danh tính caller (ARN); Google STS dùng ARN đó làm danh tính external. Cơ chế thông minh ở chỗ: chỉ ai thực sự giữ AWS credential mới ký được request hợp lệ, nên request đã ký chứng minh danh tính AWS mà không cần AWS phát hành token riêng. ARN trả về (ví dụ arn:aws:sts::123456789012:assumed-role/my-role/session) là nguồn cho attribute mapping.

External account credential configuration file

Bên ngoài GKE (workload trên AWS/Azure/CI), client library biết cách federate qua một credential configuration file kiểu external_account — file JSON mà GOOGLE_APPLICATION_CREDENTIALS trỏ tới (file 06, bước 1 của ADC). Khác hẳn key tĩnh, file này không chứa bí mật — chỉ chứa cách lấy subject token và đổi ở đâu:

json
{
  "type": "external_account",
  "audience": "//iam.googleapis.com/projects/NUM/locations/global/workloadIdentityPools/POOL/providers/PROVIDER",
  "subject_token_type": "urn:ietf:params:oauth:token-type:jwt",
  "token_url": "https://sts.googleapis.com/v1/token",
  "credential_source": { "file": "/var/run/secrets/token", "format": {"type": "text"} }
}

Vì file này vô hại nếu lộ (nó chỉ mô tả luồng, subject token vẫn phải lấy từ nguồn được tin), nó có thể commit vào repo hay nhúng vào image mà không tạo rủi ro như SA key. Đây là điểm mấu chốt khiến federation an toàn: artifact cấu hình tĩnh, credential thật thì ephemeral và sinh tại chỗ.

Attribute mapping: ánh xạ claim external sang attribute Google

Token của IdP bên ngoài chứa claim theo schema của IdP đó (GitHub có sub, repository, ref, repository_owner; AWS có arn, account). Google STS cần biết ánh xạ các claim đó sang attribute Google để dùng trong định danh và điều kiện. Đây là attribute mapping, viết bằng Common Expression Language (CEL), định dạng TARGET_ATTRIBUTE=SOURCE_EXPRESSION.

Các attribute đích theo tài liệu:

AttributeVai tròRàng buộc
google.subjectBắt buộc. Định danh duy nhất của danh tínhTối đa 127 ký tự
google.groupsTùy chọn. Tập nhóm thành viên
attribute.NAMECustom attribute để dùng trong condition/principalSetTối đa 50 attribute

Ví dụ ánh xạ:

# GitHub Actions: subject = claim sub của token GitHub
google.subject=assertion.sub

# Map thêm attribute tùy chỉnh để dùng trong condition
attribute.repository=assertion.repository
attribute.ref=assertion.ref

# CEL có thể chứa logic (ví dụ AWS — phân loại môi trường theo ARN)
attribute.environment=assertion.arn.contains(":instance-profile/Production") ? "prod" : "test"

assertion là object chứa các claim của token external. google.subject là attribute quan trọng nhất — nó là cái mà principal://...subject/SUBJECT_VALUE trỏ tới khi cấp quyền. Các attribute.NAME cho phép cấp quyền theo nhóm danh tính qua principalSet (xem dưới).

Attribute condition: chống confused deputy

Đây là phần bảo mật quan trọng nhất của file. Một attribute condition là một biểu thức CEL kiểm tra các claim/attribute và quyết định có chấp nhận token hay không. Tài liệu mô tả nó "can check assertion attributes and target attributes" và phục vụ chống confused deputy problem — tình huống một bên được tin tưởng bị lừa hành động thay cho bên không được tin.

Vì sao nó sống còn: provider OIDC như GitHub Actions có issuer chung cho toàn bộ GitHub (token.actions.githubusercontent.com). Nếu bạn chỉ cấu hình provider tin issuer đó mà không có attribute condition, thì token từ bất kỳ repo nào của bất kỳ ai đều qua được verify chữ ký và đổi được quyền GCP của bạn. Kẻ tấn công chỉ cần tạo một repo, chạy một Action, lấy OIDC token, và đổi nó. Attribute condition khóa điều này bằng cách giới hạn danh tính nào được chấp nhận:

# Chỉ chấp nhận token từ org của bạn
assertion.repository_owner == 'my-org'

# Chặt hơn: chỉ org + đúng branch main
assertion.repository_owner == 'my-org' && assertion.ref == 'refs/heads/main'

# Chỉ một repo cụ thể
assertion.repository == 'my-org/deploy-pipeline'

Tài liệu khuyến nghị thẳng dùng assertion.repository_owner=='ORGANIZATION' để "protect against spoofing". Nguyên tắc bất biến: mọi provider external phải có attribute condition khóa chặt danh tính được phép. Một provider không condition là một cửa hậu mở ra toàn bộ IdP đó.

Tấn công cụ thể, từng bước (nếu thiếu condition): Giả sử bạn tạo provider GitHub tin issuer token.actions.githubusercontent.com, mapping google.subject=assertion.sub, và quên condition; rồi cấp roles/run.developer cho principalSet://.../attribute.repository_owner/ — hoặc tệ hơn, bind cho cả pool. (1) Kẻ tấn công tạo một public repo attacker/evil trên GitHub. (2) Thêm một workflow với permissions: id-token: write. (3) Workflow gọi endpoint OIDC của GitHub để lấy một JWT — JWT này được ký hợp lệ bởi GitHub, iss đúng, aud đúng, chữ ký qua JWKS thật. (4) Workflow gửi JWT đó tới sts.googleapis.com. Vì không có condition, STS chỉ thấy "một token GitHub hợp lệ" và chấp nhận, trả federated token. (5) Nếu binding đủ rộng, token đó deploy được lên hạ tầng GCP của bạn — từ một repo kẻ tấn công kiểm soát hoàn toàn. Không có lỗ hổng phần mềm nào bị khai thác; chỉ là niềm tin được cấu hình quá rộng. Đây chính xác là confused deputy: GitHub OIDC (deputy) bị lừa chứng thực cho một danh tính mà bạn không định tin. Condition assertion.repository_owner=='my-org' chặn ở bước (4) vì evil không thuộc org của bạn — và đó là toàn bộ thứ đứng giữa cấu hình của bạn và lỗ hổng này.

Cấp quyền: principal và principalSet cho external identity

Sau khi token được chấp nhận và sinh google.subject + các attribute.*, cấp quyền dùng định danh tương tự GKE nhưng với pool tự tạo:

# Một danh tính external cụ thể (theo google.subject)
principal://iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/POOL_ID/subject/SUBJECT_VALUE

# Một nhóm theo attribute (mọi danh tính có attribute.repository = X)
principalSet://iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/POOL_ID/attribute.repository/my-org/my-repo

# Một nhóm theo group
principalSet://iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/POOL_ID/group/GROUP_ID

principalSet://...attribute.NAME/VALUE đặc biệt mạnh: nó cấp quyền cho mọi danh tính external có attribute đó bằng giá trị đó — ví dụ "mọi workflow của repo my-org/deploy". Đây là cách cấp quyền theo nhóm logic (repo, môi trường, account) thay vì từng subject.

Hai cách truy cập: direct vs impersonation

Giống file 04, có hai mô hình:

  • Direct resource access: cấp role GCP thẳng cho principal:///principalSet:// của external identity. Đơn giản, ít thành phần — khuyến nghị cho hầu hết trường hợp.
  • Service account impersonation: cấp roles/iam.workloadIdentityUser cho external identity trên một GSA; external identity mạo danh GSA. Dùng khi cần tái dùng GSA sẵn có hoặc tính năng chỉ GSA có.

Tài liệu nêu rõ hai phương thức này — "grant roles directly to external identities" vs "grant roles/iam.workloadIdentityUser to service accounts". Cùng một sự đánh đổi đơn-giản-vs-tập-trung-hóa như file 04.

Production architecture patterns

Pattern: GitHub Actions deploy không key

Mô hình chuẩn để loại SA key khỏi CI/CD:

  1. Tạo pool + provider OIDC với issuer https://token.actions.githubusercontent.com/, attribute mapping google.subject=assertion.sub, và attribute condition assertion.repository_owner=='ORG' (chặt hơn nữa: thêm && assertion.ref=='refs/heads/main').
  2. Cấp quyền deploy cho principalSet://...attribute.repository/ORG/REPO (hoặc impersonate một GSA deploy).
  3. Trong workflow, thêm permission id-token: write và dùng action google-github-actions/auth để tự lấy OIDC token và đổi lấy credential GCP.
yaml
permissions:
  id-token: write     # cho phép workflow lấy OIDC token
  contents: read
steps:
  - uses: google-github-actions/auth@v2
    with:
      workload_identity_provider: projects/NUM/locations/global/workloadIdentityPools/POOL/providers/PROVIDER
      service_account: deploy@PROJECT.iam.gserviceaccount.com   # nếu impersonation

Kết quả: không một SA key nào trong GitHub secret; mỗi lần chạy dùng một OIDC token ngắn hạn gắn với đúng repo/branch.

Pattern: workload AWS gọi Google API

Workload trên EKS/EC2 có một AWS IAM role. Tạo provider AWS tin tưởng AWS account, attribute mapping từ ARN, attribute condition giới hạn đúng role/account. Workload dùng AWS credential sẵn có (qua instance metadata của AWS) để đổi lấy federated token Google. Không có credential Google tĩnh trên AWS. Đây là cách multi-cloud đúng đắn — mỗi cloud giữ danh tính gốc của workload, federation bắc cầu tin cậy.

Pattern: một pool cho mỗi môi trường, attribute condition cho mỗi danh tính

Kết hợp hai tầng cô lập: pool tách theo môi trường (prod pool không tin IdP của dev), attribute condition tách theo danh tính trong môi trường (chỉ đúng repo/role/account). Hai tầng này cho defense-in-depth: kể cả khi một condition lỏng, ranh giới pool vẫn giới hạn thiệt hại trong một môi trường.

Common mistakes / anti-patterns

1. Provider không có attribute condition. Tạo provider GitHub/OIDC chỉ với issuer, không condition. Vì sao xảy ra: làm theo hướng dẫn rút gọn, "để chạy được trước đã". Hệ quả: confused deputy — bất kỳ ai trên IdP đó đổi được quyền GCP của bạn; lỗ hổng nghiêm trọng. Phòng tránh: không bao giờ tạo provider external thiếu condition; condition khóa org/repo/account/role là bắt buộc.

2. Condition quá lỏng (chỉ org, không repo/branch cho quyền nhạy cảm). Chỉ repository_owner=='org' cho một binding có quyền production. Vì sao xảy ra: tiện, "cả org tin được". Hệ quả: bất kỳ repo nào trong org (kể cả repo test của một nhân viên) deploy được lên prod. Phòng tránh: với quyền nhạy cảm, khóa tới đúng repo và đúng branch/tag.

3. Cấp quyền rộng cho principalSet theo attribute. Bind roles/editor cho principalSet://...attribute.repository_owner/org. Vì sao xảy ra: muốn "mọi repo của org deploy được". Hệ quả: quyền editor toàn project cho cả org GitHub. Phòng tránh: principalSet hẹp (đúng repo) + role tối thiểu (đúng quyền deploy cần).

4. Dùng một pool chung cho mọi môi trường. Một pool tin cả IdP dev lẫn prod. Vì sao xảy ra: ngại tạo nhiều pool. Hệ quả: mất một tầng cô lập; lỗi cấu hình ở dev có thể ảnh hưởng prod. Phòng tránh: một pool/môi trường theo khuyến nghị tài liệu.

5. Quên id-token: write hoặc audience sai trong CI. Workflow GitHub không khai id-token: write, hoặc audience không khớp provider. Vì sao xảy ra: thiếu permission/cấu hình. Hệ quả: runner không lấy được OIDC token, hoặc STS từ chối vì audience sai. Phòng tránh: khai đúng id-token: write; để action chính thức quản lý audience.

GCP-native implementation guidance

bash
# Tạo pool cho một môi trường external
gcloud iam workload-identity-pools create ci-prod-pool \
  --location=global --display-name="CI Prod Pool"

# Tạo provider OIDC cho GitHub Actions — CÓ attribute condition khóa org
gcloud iam workload-identity-pools providers create-oidc github \
  --location=global --workload-identity-pool=ci-prod-pool \
  --issuer-uri="https://token.actions.githubusercontent.com" \
  --attribute-mapping="google.subject=assertion.sub,attribute.repository=assertion.repository,attribute.ref=assertion.ref" \
  --attribute-condition="assertion.repository_owner=='my-org'"

# Cấp quyền cho đúng repo (direct) — principalSet theo attribute
gcloud projects add-iam-policy-binding PROJECT_ID \
  --role="roles/run.developer" \
  --member="principalSet://iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/ci-prod-pool/attribute.repository/my-org/deploy"

# (Hoặc impersonation) cho phép repo mạo danh GSA deploy
gcloud iam service-accounts add-iam-policy-binding deploy@PROJECT_ID.iam.gserviceaccount.com \
  --role="roles/iam.workloadIdentityUser" \
  --member="principalSet://iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/ci-prod-pool/attribute.repository/my-org/deploy"

Để enforce zero-key toàn tổ chức, kết hợp federation với Org Policy iam.disableServiceAccountKeyCreation (xem Chương 12, file 04) — khi tạo SA key bị cấm ở cấp tổ chức, federation trở thành con đường duy nhất, biến best-practice thành mặc định không thể lách.

Operational implications

Workload Identity Federation external dịch chuyển ranh giới tin cậy từ "ai giữ được key" sang "ai chứng minh được danh tính qua một IdP ta tin". Đây là một thay đổi sâu về mô hình vận hành bảo mật: thay vì quản lý vòng đời của các secret rải khắp CI và các đám mây, đội ngũ quản lý một tập cấu hình niềm tin khai báo — pool nào tin provider nào, với điều kiện gì. Toàn bộ "credential" trở thành ephemeral và được sinh đúng lúc cần. Hệ quả là bề mặt tấn công credential co lại đáng kể: không còn key tĩnh để đánh cắp, chỉ còn các token ngắn hạn gắn chặt với danh tính nguồn.

Đổi lại, trọng tâm vận hành chuyển sang chất lượng của attribute condition — và đây là điểm cần kỷ luật review nghiêm ngặt. Một SA key lộ là sự cố hữu hình (ai cũng hiểu "key bị lộ là xấu"); một attribute condition lỏng là lỗ hổng vô hình — hệ thống chạy hoàn hảo cho đến ngày ai đó nhận ra bất kỳ repo nào cũng deploy được lên prod. Vì vậy mọi provider external nên qua review bảo mật bắt buộc, với câu hỏi trung tâm: "chính xác danh tính nào được condition này cho phép, và đó có đúng là tập tối thiểu không?" Đây là nơi federation đòi hỏi sự cẩn trọng mà mô hình key không đòi — bù lại bằng việc loại bỏ hẳn một lớp rủi ro lớn hơn nhiều.

File 06 quay về góc thực thi: với danh tính đã được thiết lập (KSA hay external), code thực sự gọi dịch vụ Google — Secret Manager, Cloud Storage, Artifact Registry — như thế nào qua Application Default Credentials.

References