Skip to content

Authentication & Identity — Mô Hình Hai Cổng IAM/RBAC

Vì sao phân biệt authentication và authorization là sống còn

Privilege escalation trong GKE hầu như luôn bắt nguồn từ việc nhập nhằng hai câu hỏi tách biệt:

  1. Authentication (xác thực): "Bạn là ai?" — chứng minh danh tính.
  2. Authorization (phân quyền): "Bạn được làm gì?" — quyết định quyền hạn.

Trên GKE, hai câu hỏi này được trả lời bởi hai hệ thống khác nhau, ở hai tầng khác nhau: Google IAM và Kubernetes (RBAC + authenticator). Đội ngũ hiểu sai thường tin rằng "đăng nhập bằng tài khoản Google của công ty là đủ an toàn". Nhưng việc xác thực được không nói gì về việc được phép làm gì — và ngược lại, một identity được IAM cấp quyền rộng có thể bypass hoàn toàn lớp RBAC tinh vi mà bạn dày công thiết kế.

File này tách bạch hai tầng đó, giải thích chính xác một request kubectl đi qua những cổng nào, ai kiểm soát cổng nào, và vì sao một quyết định IAM tưởng vô hại có thể vô hiệu hóa toàn bộ RBAC.

Internal model: một request đi qua những cổng nào

Khi bạn chạy kubectl get pods, request đi qua một chuỗi cổng theo đúng thứ tự. Hiểu chuỗi này là chìa khóa của toàn bộ chương:

kubectl
  → [Cổng 0: IAM] gcloud container clusters get-credentials cần roles/container.* để lấy được kubeconfig
  → gke-gcloud-auth-plugin sinh OAuth 2.0 access token từ Google identity
  → HTTPS tới API server endpoint (private/authorized networks kiểm soát ở đây)
  → [Cổng 1: Authentication] API server xác thực token qua webhook tới Google
        → trả về identity: email + các group (vd: system:authenticated)
  → [Cổng 2: Authorization - RBAC] API server kiểm tra RBAC: identity này có quyền 'get pods' trong namespace này?
  → [Cổng 3: Admission] admission webhooks (Chương 10) — không phải authz nhưng có thể chặn
  → thực thi

Bốn cổng, bốn hệ thống kiểm soát khác nhau:

  • Cổng 0 (IAM): bạn cần một IAM role chứa permission container.clusters.get để chạy get-credentials và lấy kubeconfig. Không có nó, bạn không kết nối được cluster ngay từ đầu.
  • Cổng 1 (Authentication): API server không tự xác thực Google token — nó gọi ngược lại Google qua một webhook token authenticator do GKE cấu hình. Google trả về danh tính (email người dùng hoặc service account) và các Kubernetes group mà danh tính này thuộc về.
  • Cổng 2 (Authorization): đây là RBAC. API server kiểm tra xem identity (và group của nó) có RoleBinding/ClusterRoleBinding cấp quyền cho động từ + tài nguyên đang yêu cầu không.
  • Cổng 3 (Admission): validating/mutating webhooks — đã trình bày ở Chương 10, không phải authorization nhưng là cổng chặn cuối.

Điểm mấu chốt: IAM (cổng 0) và RBAC (cổng 2) là hai cổng độc lập, và bạn cần vượt cả hai. Theo tài liệu Access control with IAM: "An entity must have sufficient RBAC and IAM permissions to work with resources in your cluster." — một thực thể phải có cả quyền RBAC lẫn quyền IAM đủ để thao tác. Đây là mô hình hai cổng (two-gate).

Mô hình hai cổng: IAM authorize kết nối, RBAC authorize hành động

Hãy hình dung rạch ròi vai trò:

Cổng IAMCổng RBAC
Câu hỏi"Được kết nối/quản lý cluster nào?""Trong cluster, được thao tác gì?"
Phạm viProject / cluster-level (GCP resource)Namespace / cluster-level (K8s object)
Ai enforceGCP IAM (Google)Kubernetes API server
GranularityThô: cluster, các thao tác container.*Mịn: verb × resource × namespace
Ví dụ permissioncontainer.clusters.get, container.pods.listget/list/create trên pods, secrets

Trong trường hợp lý tưởng, hai cổng bổ sung cho nhau: IAM cấp quyền tối thiểu để kết nối (ví dụ roles/container.clusterViewer — chỉ list/get cluster), rồi RBAC mới quyết định chi tiết bên trong (ví dụ chỉ view trong namespace team-a). Đây là cách thiết kế least-privilege đúng: IAM hẹp + RBAC chi tiết.

Cái bẫy: IAM role rộng ánh xạ ngầm sang RBAC quyền lớn

Đây là phần dễ hiểu sai nhất và là nguồn gốc của hầu hết privilege escalation. Một số GKE IAM role chứa sẵn các permission Kubernetes tương đương, và GKE ánh xạ chúng vào quyền RBAC một cách ngầm. Cụ thể:

  • roles/container.developer → cho phép thao tác hầu hết Kubernetes object (tương đương quyền rộng trong cluster, không bị RBAC giới hạn thêm).
  • roles/container.admin → quyền quản lý đầy đủ cluster Kubernetes API object — thực chất tương đương cluster-admin.

Hệ quả nghiêm trọng: nếu bạn cấp roles/container.admin ở tầng IAM, bạn đã bypass RBAC. Dù bạn có viết RBAC chặt đến đâu, một principal có container.admin vẫn làm được mọi thứ, vì quyền của họ đến từ cổng IAM chứ không qua cổng RBAC bạn kiểm soát. Đây là lý do tài liệu khuyến nghị: với người dùng cần quyền chi tiết trong cluster, hãy cấp IAM role tối thiểu để kết nối (container.clusterViewer) rồi dùng RBAC để cấp quyền cụ thể, thay vì dùng IAM role rộng.

Quy tắc thực hành: IAM để vào cửa, RBAC để đi trong nhà. Cấp IAM rộng giống như đưa chìa khóa vạn năng — RBAC dù tinh vi đến đâu cũng vô nghĩa.

Một hệ quả tinh tế: vì cả người dùng đã xác thực đều thuộc group system:authenticated, một ClusterRoleBinding gắn vào system:authenticated cấp quyền cho mọi người xác thực được — kể cả những người chỉ có quyền IAM tối thiểu. Đây là một anti-pattern chí mạng sẽ phân tích sâu ở file 03.

Các phương thức authentication trên GKE

1. Google identity (người dùng) qua OAuth/OIDC

Phương thức chính cho con người. Người dùng đăng nhập bằng tài khoản Google (Cloud Identity / Workspace), gcloud auth login lấy OAuth credential, và gke-gcloud-auth-plugin (một client-go credential plugin) sinh OAuth 2.0 access token cho mỗi request kubectl. Token này short-lived (khoảng một giờ) và tự refresh. API server xác thực nó qua webhook về Google.

Đây là phương thức an toàn nhất cho con người vì: không có credential dài hạn lưu trên máy, MFA của tổ chức được áp dụng ở tầng Google, và việc revoke truy cập chỉ cần xóa IAM binding — không cần xoay vòng certificate.

Lưu ý lịch sử: trước đây GKE auth nằm trong gcloud trực tiếp; hiện tại kubectl yêu cầu plugin riêng gke-gcloud-auth-plugin (cài qua gcloud components install gke-gcloud-auth-plugin). Đây là thay đổi theo chuẩn client-go exec credential plugin của Kubernetes.

2. IAM service account (workload và automation)

Cho CI/CD pipeline, automation, và workload gọi Kubernetes API. Một IAM service account xác thực bằng cách sinh OAuth token (qua key, hoặc tốt hơn là qua Workload Identity — file 04). Service account cũng đi qua đúng mô hình hai cổng: cần IAM role để kết nối, và RBAC binding để thao tác.

Với automation chạy bên trong cluster gọi Kubernetes API (không phải GCP API), thường dùng Kubernetes ServiceAccount (khác IAM service account — xem phần dưới). Với automation gọi GCP API, dùng IAM service account qua Workload Identity.

3. OIDC third-party

GKE hỗ trợ liên kết identity provider bên thứ ba (Okta, Azure AD, v.v.) qua Identity Service for GKE / OIDC, cho phép xác thực vào cluster bằng IdP doanh nghiệp mà không cần Google identity cho từng người. Hữu ích cho tổ chức đã chuẩn hóa trên một IdP. Token OIDC được API server xác thực và ánh xạ sang user/group để RBAC xử lý.

4. X.509 client certificate (legacy — nên tắt)

Kubernetes hỗ trợ xác thực bằng client certificate ký bởi cluster CA. Trên GKE đây là một legacy authentication method: certificate dài hạn, khó revoke (không có CRL tiện lợi), và nếu lộ thì phải xoay vòng cả CA. Tài liệu hardening khuyến nghị tắt client certificate issuance (--no-issue-client-certificate, và đảm bảo masterAuth.clientCertificateConfig không bật). Certificate static không gắn với IAM nên không hưởng lợi từ MFA, audit, hay revoke tập trung.

5. Basic auth (static password) và ABAC — đã loại bỏ

Các cluster GKE rất cũ từng hỗ trợ HTTP basic auth (username/password tĩnh) và ABAC (Attribute-Based Access Control). Cả hai đều là rủi ro bảo mật nghiêm trọng (credential tĩnh, phân quyền thô) và đã bị vô hiệu hóa mặc định / loại bỏ trên các phiên bản GKE hiện đại. Nếu kế thừa một cluster cũ, kiểm tra và tắt ngay: basic auth phải tắt (--no-enable-basic-auth), ABAC phải tắt (chỉ dùng RBAC).

Theo tài liệu hardening của Google Cloud, bạn nên "disable legacy API server authentication methods like static certificates and passwords" — tắt mọi phương thức xác thực kế thừa dùng certificate tĩnh hoặc mật khẩu.

ServiceAccount token: legacy vs bound token

Kubernetes ServiceAccount (KSA) là identity cho process chạy trong Pod khi chúng gọi Kubernetes API. Đây là một identity khác với IAM service account, và cơ chế token của nó đã thay đổi quan trọng về mặt bảo mật.

Legacy token (Secret-based) — mô hình cũ, nguy hiểm

Trước Kubernetes 1.24, mỗi KSA tự động được cấp một Secret chứa token JWT không hết hạn. Token này:

  • Không có thời hạn — lộ một lần là lộ vĩnh viễn cho đến khi xóa thủ công.
  • Không gắn audience — dùng được cho bất kỳ ai chấp nhận token KSA.
  • Nằm trong etcd và được mount tự động vào mọi Pod dùng KSA đó.

Vector tấn công kinh điển: chiếm một Pod, đọc /var/run/secrets/kubernetes.io/serviceaccount/token, và có một credential vĩnh viễn của KSA đó.

Bound token (TokenRequest API) — mô hình hiện đại, mặc định

Từ Kubernetes 1.24+ (và GKE hiện đại), token được cấp qua TokenRequest API và mount qua projected volume. Bound token:

  • Có thời hạn (mặc định ~1 giờ, kubelet tự refresh) — lộ token chỉ nguy hiểm trong cửa sổ ngắn.
  • Gắn audience — chỉ dùng được cho đúng đối tượng nhận (API server, hoặc audience cụ thể).
  • Bound vào Pod — token vô hiệu khi Pod bị xóa, không tồn tại độc lập.

Đây là một cải thiện bảo mật lớn: bán kính thiệt hại của một token bị lộ giảm từ "vĩnh viễn, ở mọi nơi" xuống "một giờ, đúng audience, đúng Pod".

Khuyến nghị thực hành

  • Tắt automount khi không cần: nếu Pod không gọi Kubernetes API, đặt automountServiceAccountToken: false ở Pod hoặc ServiceAccount. Mỗi token được mount là một credential có thể bị lộ — không mount thứ không dùng.
  • Một KSA riêng cho mỗi workload: đừng để mọi Pod dùng default ServiceAccount. KSA riêng cho phép RBAC tối thiểu theo từng workload, và giới hạn thiệt hại khi một workload bị chiếm.
  • Dùng audience-bound token cho service-to-service: khi cần token để gọi một dịch vụ nội bộ, dùng projected token với audience cụ thể thay vì token API server mặc định.
yaml
# Pod không cần gọi Kubernetes API → không mount token
apiVersion: v1
kind: Pod
metadata:
  name: frontend
spec:
  serviceAccountName: frontend-sa
  automountServiceAccountToken: false   # cắt một vector lộ credential
  containers:
    - name: app
      image: registry.example.com/frontend@sha256:...
yaml
# Projected bound token với audience cụ thể (thời hạn ngắn, đúng đối tượng)
apiVersion: v1
kind: Pod
metadata:
  name: caller
spec:
  serviceAccountName: caller-sa
  containers:
    - name: app
      image: registry.example.com/caller@sha256:...
      volumeMounts:
        - name: api-token
          mountPath: /var/run/secrets/tokens
  volumes:
    - name: api-token
      projected:
        sources:
          - serviceAccountToken:
              path: internal-api-token
              audience: internal-api.example.com   # token chỉ dùng được với audience này
              expirationSeconds: 3600

Production architecture patterns

Pattern: con người dùng Google identity, máy dùng Workload Identity

Một ranh giới sạch: con người xác thực qua Google identity (OAuth/OIDC, hưởng MFA và audit của tổ chức), workload xác thực qua Workload Identity (file 04, không key tĩnh), và CI/CD dùng Workload Identity Federation từ runner (GitHub Actions, GitLab) tới Google. Nguyên tắc xuyên suốt: không có long-lived credential nào tồn tại trên đĩa hay trong Git ở bất kỳ đâu trong chuỗi.

Pattern: ánh xạ group của IdP sang RBAC

Thay vì binding RBAC cho từng email người dùng (không scale, khó audit), ánh xạ Google Group (qua Google Workspace) hoặc group từ OIDC IdP sang RBAC. Một RoleBinding gắn vào group gke-team-a-developers@company.com; thêm/bớt người chỉ là quản lý thành viên group ở tầng IdP. GKE hỗ trợ Google Groups for RBAC, cho phép API server resolve group membership. Điều này biến quản lý quyền thành quản lý group — tập trung, audit được, revoke tức thì.

Common mistakes / anti-patterns

1. Cấp roles/container.admin cho cả team. Vì RBAC "phiền", admin cấp luôn IAM role rộng. Hệ quả: RBAC bị bypass, mọi developer là cluster-admin, một credential lộ = toàn cluster. Phòng tránh: IAM tối thiểu (clusterViewer) + RBAC chi tiết.

2. Bind RBAC cho từng email thay vì group. Hệ quả: 200 RoleBinding rải rác, không ai biết ai có quyền gì, người rời công ty vẫn còn binding. Phòng tránh: bind cho group, quản lý membership ở IdP.

3. Để mọi Pod dùng default ServiceAccount với token automount. Hệ quả: mọi Pod mang một credential KSA, và default thường tích lũy quyền qua thời gian. Phòng tránh: KSA riêng mỗi workload, automountServiceAccountToken: false khi không cần.

4. Còn bật client certificate / basic auth trên cluster kế thừa. Hệ quả: credential tĩnh không revoke được, không MFA, không audit cá nhân. Phòng tránh: tắt tất cả legacy auth, chỉ dùng Google identity/OIDC + RBAC.

5. Nhầm Kubernetes ServiceAccount với IAM service account. Hai thứ tên giống nhau nhưng khác hệ thống: KSA cho Kubernetes API, IAM SA cho GCP API. Hệ quả: cấu hình Workload Identity sai, gán quyền nhầm chỗ. Phòng tránh: luôn nói rõ "KSA" hay "IAM SA", và hiểu Workload Identity chính là cây cầu nối hai loại này (file 04).

GCP-native implementation guidance

bash
# Kiểm tra một identity có thể làm gì trong cluster (RBAC) — cổng 2
kubectl auth can-i create pods --namespace team-a
kubectl auth can-i '*' '*' --all-namespaces   # nếu trả 'yes' => quyền quá rộng, điều tra

# Xem một user/SA cụ thể (cần quyền impersonate)
kubectl auth can-i list secrets --namespace team-a \
  --as=user@company.com

# Kiểm tra legacy auth đã tắt chưa — cổng 1
gcloud container clusters describe CLUSTER --region REGION \
  --format="value(masterAuth.clientCertificateConfig.issueClientCertificate)"
# Kỳ vọng: rỗng/false (client certificate đã tắt)

# Xem IAM bindings ở tầng project (cổng 0) — soi quyền container.* rộng
gcloud projects get-iam-policy PROJECT_ID \
  --flatten="bindings[].members" \
  --filter="bindings.role:roles/container.admin" \
  --format="table(bindings.members)"

kubectl auth can-i là công cụ kiểm chứng quan trọng nhất khi thiết kế RBAC: nó trả lời trực tiếp câu hỏi cổng 2 cho một identity cụ thể, và là cách nhanh nhất phát hiện quyền dư thừa.

Operational implications

Mô hình hai cổng có một hệ quả vận hành quyết định cách bạn audit: để biết một người thực sự làm được gì trong cluster, bạn phải kiểm tra cả hai cổng cùng lúc. Chỉ nhìn RBAC là chưa đủ — một người có RBAC view nhưng IAM container.admin thực chất là admin. Chỉ nhìn IAM cũng chưa đủ — một người có IAM tối thiểu nhưng RBAC cluster-admin qua một binding nào đó cũng là admin. Audit quyền đúng nghĩa là giao của hai tập, với lưu ý các IAM role rộng ghi đè RBAC.

Hệ quả thứ hai liên quan đến offboarding: revoke một người chỉ hoàn tất khi cả hai cổng đều bị thu hồi. Xóa IAM binding mà còn RBAC binding gắn email, hoặc ngược lại, để lại đường vào còn sót. Đây là lý do pattern "bind cho group, quản lý ở IdP" mạnh hơn nhiều: tắt một tài khoản ở IdP đóng cả hai cổng cùng lúc.

References