Tăng cường bảo mật etcd và Kubernetes API Server: Mã hóa dữ liệu tĩnh, mTLS và phát hiện rò rỉ thông tin xác thực

Security tutorial - IT technology blog
Security tutorial - IT technology blog

Rủi ro tiềm ẩn trong etcd của bạn

Sau khi server của tôi bị tấn công SSH brute-force vào lúc nửa đêm, tôi đã phát triển đúng loại sự hoang mang về bảo mật. Vì vậy khi bắt đầu quản lý các cluster Kubernetes production, điều đầu tiên tôi làm không phải là triển khai workload — tôi đi thẳng vào control plane và tự hỏi: chuyện gì xảy ra nếu có người xâm nhập được vào etcd?

Câu trả lời không mấy dễ chịu. etcd là bộ não của Kubernetes. Nó lưu trữ mọi Secret, ConfigMap, ServiceAccount token, và mọi thay đổi trạng thái cluster từng được ghi lại. Theo mặc định, trên nhiều cấu hình khởi tạo cluster — bao gồm một số cấu hình mặc định của kubeadm — Secrets được lưu dưới dạng văn bản mã hóa base64. Không phải mã hóa thật sự. Chỉ là encode. Bất kỳ ai có quyền truy cập etcd đều có thể đọc mật khẩu database, khóa riêng TLS và API token của bạn chỉ bằng một lệnh etcdctl get.

Các endpoint etcd bị lộ xuất hiện trong các báo cáo sự cố bảo mật thực tế như những vector xâm nhập ban đầu — không phải giả thuyết, mà là các sự cố được ghi chép rõ ràng. Để xử lý đúng điều này đòi hỏi cấu hình có chủ ý mà hầu hết các nhóm thường bỏ qua trong lúc vội vàng để có thứ gì đó chạy được. Đây là những gì tôi làm trên mỗi cluster: mã hóa dữ liệu tĩnh, xác minh mTLS qua các thành phần control plane, và quét rò rỉ thông tin xác thực. Cả ba, mỗi lần.

Các khái niệm cốt lõi trước khi chỉnh sửa cấu hình

Mã hóa dữ liệu tĩnh và mã hóa trong quá trình truyền

Hai khái niệm này giải quyết hai vấn đề riêng biệt. In transit (trong quá trình truyền) nghĩa là dữ liệu được mã hóa khi di chuyển giữa các thành phần — kube-apiserver giao tiếp với etcd qua TLS, kubelet báo cáo trạng thái node qua HTTPS. Kubernetes xử lý điều này khá tốt theo mặc định khi được khởi tạo đúng cách.

At rest (dữ liệu tĩnh) nghĩa là dữ liệu nằm trên đĩa bên trong etcd được mã hóa. Tính năng này mặc định bị tắt. Nếu ai đó lấy được volume đĩa etcd — đánh cắp snapshot, VM bị xâm phạm, cloud snapshot bất hợp pháp — họ sẽ nhận được Secrets dưới dạng văn bản thuần. Bật mã hóa dữ liệu tĩnh sử dụng resource EncryptionConfiguration để yêu cầu kube-apiserver mã hóa các loại resource cụ thể trước khi ghi vào etcd.

Mutual TLS trong Control Plane

TLS thông thường chứng minh danh tính của server với client. Mutual TLS (mTLS) hoạt động theo cả hai chiều — cả hai phía đều trình bày chứng chỉ và xác minh lẫn nhau. Trong một control plane Kubernetes:

  • kube-apiserver kết nối với etcd bằng chứng chỉ client
  • etcd chỉ chấp nhận kết nối từ các client trình bày chứng chỉ được ký bởi CA tin cậy của nó
  • kubelet trình bày chứng chỉ node cho kube-apiserver với mỗi lần gọi API

Các cluster được khởi tạo bằng kubeadm cấu hình điều này đúng cách. Tuy nhiên, trong môi trường tùy chỉnh hoặc managed, dễ dàng dẫn đến tình trạng thiếu flag, chuỗi CA sai, hoặc chứng chỉ hết hạn một cách lặng lẽ. Chúng trông có vẻ hoạt động bình thường. Nhưng thực ra không phải vậy.

Nơi thông tin xác thực thực sự bị rò rỉ

Thông tin xác thực rò rỉ khỏi control plane qua ba con đường chính: dữ liệu dạng văn bản thuần trong etcd, JWT service account token được mount vào các pod không cần đến chúng, và các file kubeconfig chứa thông tin xác thực admin tồn tại lâu dài được sao chép qua nhiều máy. Cả ba đều cần được kiểm soát chặt chẽ.

Thực hành: Khóa chặt Control Plane

Bước 1 — Bật mã hóa dữ liệu tĩnh cho Secrets

Tạo file cấu hình mã hóa trên node control plane của bạn. File này xác định những resource nào được mã hóa và bằng provider nào:

# /etc/kubernetes/encryption-config.yaml
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
      - secrets
      - configmaps
    providers:
      - aescbc:
          keys:
            - name: key1
              secret: <BASE64_ENCODED_32_BYTE_KEY>
      - identity: {}

Tạo khóa 32-byte:

head -c 32 /dev/urandom | base64

Trên cluster kubeadm, chỉnh sửa manifest static pod của kube-apiserver tại /etc/kubernetes/manifests/kube-apiserver.yaml để tham chiếu đến file cấu hình:

spec:
  containers:
  - command:
    - kube-apiserver
    - --encryption-provider-config=/etc/kubernetes/encryption-config.yaml
    volumeMounts:
    - mountPath: /etc/kubernetes/encryption-config.yaml
      name: encryption-config
      readOnly: true
  volumes:
  - hostPath:
      path: /etc/kubernetes/encryption-config.yaml
      type: File
    name: encryption-config

Sau khi kube-apiserver khởi động lại, các Secret mới sẽ được mã hóa. Các Secret cũ không được mã hóa lại tự động — hãy ép ghi lại:

kubectl get secrets --all-namespaces -o json | kubectl replace -f -

Xác minh mã hóa thực sự đang hoạt động bằng cách đọc một Secret trực tiếp từ etcd:

ETCDCTL_API=3 etcdctl \
  --endpoints=https://127.0.0.1:2379 \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key \
  get /registry/secrets/default/my-secret | hexdump -C | head -5

Nếu thấy k8s:enc:aescbc:v1:key1 là tiền tố trong đầu ra, mã hóa đang hoạt động. Nếu thấy văn bản đọc được, thì chưa.

Bước 2 — Xác minh mTLS giữa etcd và kube-apiserver

Kiểm tra các flag của kube-apiserver đang chạy để xác nhận nó đang trình bày chứng chỉ client cho etcd:

ps aux | grep kube-apiserver | tr ' ' '\n' | grep '\-\-etcd-'

Bạn sẽ thấy --etcd-cafile, --etcd-certfile, và --etcd-keyfile đều trỏ đến các file thực tế. Nếu thiếu bất kỳ flag nào, etcd đang chấp nhận kết nối không xác thực từ API server.

Hết hạn chứng chỉ là thứ âm thầm gây rắc rối cho các nhóm:

# Kiểm tra ngày hết hạn tất cả chứng chỉ control plane
kubeadm certs check-expiration

# Kiểm tra thủ công các chứng chỉ etcd cụ thể
openssl x509 -in /etc/kubernetes/pki/etcd/server.crt -noout -dates
openssl x509 -in /etc/kubernetes/pki/apiserver-etcd-client.crt -noout -dates

Tiếp theo, xác minh etcd không vô tình lắng nghe trên tất cả các interface:

ss -tlnp | grep 2379

Nếu thấy 0.0.0.0:2379, etcd đang bị lộ ra mạng. Flag --listen-client-urls trong cấu hình etcd nên được gắn với 127.0.0.1 hoặc chỉ IP nội bộ của cluster.

Bước 3 — Quét thông tin xác thực bị rò rỉ trong Secrets

Ngay cả khi bật mã hóa dữ liệu tĩnh, thông tin xác thực vẫn có thể rò rỉ qua các con đường khác. Bắt đầu bằng cách lập danh sách những gì thực sự được lưu trong Secrets trên toàn cluster:

# Liệt kê tất cả secret và các key của chúng theo namespace
kubectl get secrets --all-namespaces -o json | \
  jq -r '.items[] | .metadata.namespace + "/" + .metadata.name + ": " + \
  (.data | if . then (keys | join(", ")) else "(không có dữ liệu)" end)'

Đối với các Secret bạn muốn kiểm tra thủ công:

kubectl get secret my-secret -n default \
  -o jsonpath='{.data.password}' | base64 -d

Trong quá trình xử lý sự cố, tôi dùng Base64 Encoder/Decoder của ToolCraft để kiểm tra nhanh thủ công. Nó chạy hoàn toàn trong trình duyệt — không có gì rời khỏi máy của bạn. Điều này rất quan trọng khi xử lý các giá trị encode nhạy cảm. Dán secret vào một công cụ online ghi lại đầu vào chính xác là loại rò rỉ mà bạn đang cố ngăn chặn.

Ngoài bản thân các secret, hãy kiểm tra pod nào đang nhận service account token mà chúng có thể không cần:

# Tìm các pod mà automount không được tắt tường minh
kubectl get pods --all-namespaces -o json | \
  jq -r '.items[] | select(.spec.automountServiceAccountToken != false) | \
  .metadata.namespace + "/" + .metadata.name'

Khi lấy JWT token từ một pod đang chạy để kiểm tra:

kubectl exec -n default my-pod -- \
  cat /var/run/secrets/kubernetes.io/serviceaccount/token

Công cụ JWT Decoder của ToolCraft cho phép bạn đọc các claim, thời gian hết hạn và issuer trực tiếp trong trình duyệt mà không gửi token đi đâu cả. Hữu ích để xác nhận phạm vi token có bị giới hạn phù hợp không hoặc khi nào nó hết hạn.

Bước 4 — Kiểm toán RBAC để đánh giá mức độ ảnh hưởng của thông tin xác thực bị rò rỉ

Thông tin xác thực bị rò rỉ chỉ nguy hiểm đến mức quyền hạn được gắn với nó. Kiểm tra các service account có cluster-admin:

kubectl get clusterrolebindings -o json | \
  jq -r '.items[] | select(.roleRef.name == "cluster-admin") | \
  .metadata.name + ": " + ([.subjects[]?.name] | join(", "))'

Bất kỳ service account nào có cluster-admin mà không được ghi lại và lý giải rõ ràng đều là rủi ro đang tồn tại trong cluster của bạn ngay lúc này.

Duy trì bảo mật lâu dài

Cluster thay đổi theo thời gian. Chứng chỉ hết hạn, các Secret mới được thêm vào mà không cần suy nghĩ kỹ, các ràng buộc RBAC tích lũy âm thầm theo tháng. Tôi đã xây dựng ba kiểm tra định kỳ trong runbook cluster của mình:

  1. Hàng tháng: chạy kubeadm certs check-expiration và đánh dấu bất kỳ chứng chỉ nào hết hạn trong vòng 90 ngày
  2. Sau mỗi deployment mới: xác minh ServiceAccount của workload không mount token mà nó không sử dụng (automountServiceAccountToken: false trong pod spec)
  3. Hàng quý: quét lại các binding cluster-admin và kiểm tra mã hóa etcd vẫn đang hoạt động

Đây là cùng một nguyên tắc tôi áp dụng cho việc xoay vòng SSH key và ngưỡng Fail2Ban trên mỗi Linux host — chỉ là hướng đến Kubernetes control plane thay vì.

etcd bị lộ đã là điểm xâm nhập trong các sự cố production thực tế. Các biện pháp giảm thiểu được tích hợp sẵn trong Kubernetes, các bước xác minh chỉ mất vài phút, và các lệnh trên bao phủ toàn bộ bề mặt tấn công. Hãy bắt đầu với mã hóa dữ liệu tĩnh — nó mang lại tác động cao nhất với ít rủi ro nhất cho cluster đang chạy.

Share: