Exeternal Secret Operator - Quản lý Secret trên Kubernetes
Giới thiệu
What's up các bạnnnn, dạo này khi đang ngồi đọc 1 topic về lưu secret trong cụm K8s ở Stack Overflow, mình vô tình đọc được một dòng bình luận khá hay: "Secret trong Kubernetes thực chất chỉ là một chiếc hộp dán nhãn 'Cấm mở', nhưng ai cũng có thể đọc được nội dung chỉ bằng một lệnh Base64 đơn giản." =))
Mặc dù có vẻ hài hước nhưng lại phản ánh một vấn đề thực tế: Kubernetes Secret mặc định không phải là một giải pháp lưu trữ an toàn tuyệt đối. Trong môi trường doanh nghiệp và nhất là production, chúng ta cần một giải pháp quản lý tập trung, có tính kiểm soát cao và tách biệt hoàn toàn giữa hạ tầng với dữ liệu nhạy cảm. 
Trong lúc làm các dự án, mình nhận thấy External Secrets Operator (ESO) là 1 phương án khá phù hợp giúp kết nối Kubernetes với các credentials storage như AWS Secrets Manager hay HashiCorp Vault.
1. Cách ESO hoạt động

Thay vì việc bạn phải thủ công tạo Secret bằng tay hoặc lưu chúng trong file cấu hình, ESO tự động hóa quá trình này. Nó hoạt động như một cầu nối, đảm bảo rằng Secret trong Cluster luôn là bản sao mới nhất và an toàn nhất từ credentials storage.
Workflow của ESO:

- Tiếp nhận yêu cầu (External Secrets Object): Bạn khai báo request lấy Secret từ AWS thông qua file YAML để tạo External Secret
- Xác thực thông tin (Secret Store): ESO kiểm tra cấu hình kết nối và phương thức xác thực (IRSA).
- Kết nối và Truy xuất (Connects & Fetches): Sử dụng quyền hạn từ IAM Role, ESO kéo dữ liệu từ AWS Secrets Manager.
- Khởi tạo và Đồng bộ (Creates & Syncs): ESO tạo ra một Kubernetes Secret chuẩn và liên tục cập nhật (Sync) nếu có thay đổi từ phía AWS.
Để triển khai, chúng ta cần nắm vững ba thànhh phần chính:
- ClusterSecretStore: Định nghĩa cách thức Cluster kết nối tới nhà cung cấp (AWS, Vault...). Đây là cấu hình dùng chung ở cấp cụm cluster
- SecretStore: Tương tự như trên nhưng được giới hạn trong phạm vi một Namespace cụ thể, giúp phân quyền quản lý tốt hơn.
- ExternalSecret: Đây là object quan trọng nhất. Nó giống như mrột thông báo "Tôi lấy Secret A từ AWS Secret Manage , mapping vào K8s Secret B và cập nhật nó mỗi C giây"
2. Các bước triển khai chi tiết
Để bắt đầu, hãy chuẩn bị cho mình 1 số resource:
- AWS CLI
- kubectl
- Helm
- Một cụm EKS đã bật OIDC provider nhé, cách tạo 1 cụm EKS có thể tham khảo: https://dev.to/aws-builders/create-a-cluster-in-amazon-eks-and-install-kubectl-4664. (Sau này mình tự viết bài sẽ update thành cách của mình sau nha)

- AWS Secret Manager (các bạn qua GUI cho đơn giản)
Sau khi chuẩn bị, ta sẽ có 1 cụm EKS và AWS Secret Manager như sau


Bước 1: Khởi tạo IAM Policy cho ESO
Chúng ta sẽ tạo một IAM Policy chỉ cho phép ESO đọc các Secret cần thiết. Thay vì dùng dấu *, bạn nên giới hạn Resource cụ thể để đảm bảo an toàn tối đa, còn trong bài này mình sẽ để * cho đơn giản.
# Tạo file policy định nghĩa quyền truy cập
cat << EOF > eso-policy.json
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowAccessToSecretsManager",
"Effect": "Allow",
"Action": [
"secretsmanager:ListSecrets",
"secretsmanager:GetSecretValue",
"secretsmanager:DescribeSecret",
"secretsmanager:ListSecretVersionIds"
],
"Resource": "*"
}
]
}
EOF
# Tạo policy trên AWS
export POLICY_ARN=$(aws iam create-policy \
--policy-name datnx-eks-staging-ESOPolicy \
--policy-document file://eso-policy.json \
--query 'Policy.Arn' --output text)
echo "Policy ARN: $POLICY_ARN"
Bước 2: Cấu hình IAM Role cho Service Account (IRSA)
Ở các version eks sau 1.30+, AWS có support Pod Identity, tuy nhiên mình vẫn sẽ sử dụng IRSA để gán quyền cho Pod để đảm bảo sử dụng được cho các bạn version cũ.
Mục đích ta sẽ tạo 1 Service Account external-secrets trong namespace external-secrets với Role và Policy đã đề cập ở trên.
# Tạo Service Account gắn kèm IAM Role thông qua eksctl
eksctl create iamserviceaccount \
--name external-secrets \
--namespace external-secrets \
--cluster datnx-eks-staging \
--role-name datnx-eks-staging-ESORole
--attach-policy-arn $POLICY_ARN \
--approve \
--override-existing-serviceaccounts
Bước 3: Cài đặt External Secrets Operator
Sau khi đã có IRSA, chúng ta tiến hành cài đặt ESO bằng Helm, mình sẽ dùng chart chính chủ của ESO luôn.
Thường mình sẽ pull file values về và overrides, sau đó apply với file values riêng. Tuy nhiên để đảm bảo độ tiếp cận 1 cách đơn giản nhất có thể, mình set sẵn trong lệnh install cho các bạn cài đặt 1 cách thuận tiện hơn.
# Thêm repo và cập nhật
helm repo add external-secrets https://charts.external-secrets.io
helm repo update
# Cài đặt ESO và sử dụng Service Account đã tạo ở Bước 2
helm install external-secrets external-secrets/external-secrets \
--namespace external-secrets \
--create-namespace \
--set serviceAccount.create=false \
--set serviceAccount.name=external-secrets \
--set installCRDs=true
# Verify
kubectl get pods -n external-secrets
Sau khi triển khai xong, ta sẽ được như ảnh dưới

Bước 4 Tạo SecretStore & ExternalSecret
Bây giờ là lúc chúng ta cấu hình để ESO thực sự làm việc.
Khởi tạo ClusterSecretStore
File này đóng vai trò là "chỉ dẫn" để ESO biết phải đi đâu để lấy credentials. Ta sẽ dùng service: SecretsManager và region là region của service đó (của mình đang ở us-east-1).
secretstore.yaml:
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
name: aws-secrets-manager
spec:
provider:
aws:
service: SecretsManager
region: "us-east-1# Thay bằng region của bạn
auth:
jwt:
serviceAccountRef:
name: external-secrets
namespace: external-secrets

Status là Valid và Ready để sử dụng
Tạo ExternalSecret
Đây là lúc bạn mapping dữ liệu từ AWS về Kubernetes. Mình sẽ tạo trong namespace staging để lab.
# externalsecret.yaml
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: api-secrets
namespace: staging
spec:
refreshInterval: 5s # Check for updates every 5 seconds
secretStoreRef:
name: aws-secrets-manager
kind: ClusterSecretStore
target:
name: api-secrets # K8s Secret name
creationPolicy: Owner
dataFrom:
- extract:
key: datnx-api # AWS Secrets Manager secret name
#Verify
kubectl get externalsecret -n staging
Như vậy đã tạo thành công ExternalSecret, và khi ta check secret, ta sẽ thấy có 1 secret “api-secrets” được tạo tương ứng

Bước 5: Verify Secret
Ta sẽ add credentials trên AWS Secret Manager, ở đây mình add credentials “PORT: 5001” của app làm ví dụ với hy vọng rằng credentials này sẽ sync về secret “api-staging”

Trong cụm, chạy command sau để lấy secret value
kubectl get secret api-secrets -n staging -o yaml

Value của PORT đang ở dạng encoded, ta cùng decode secret này ra xem có khớp không
echo 'NTAwMQ===' | base64 --decode

Như vậy, secret đã được sync từ AWS Secret Manager qua ESO thành công. đạt target trong bài lab này.
Bước 6 Clean up
Sau khi lab xong, 1 thói quen mình mong các bạn rèn dần là clean up các resources sau khi sử dụng xong.
kubectl delete externalsecret api-secrets -n staging
kubectl delete clustersecretstore aws-secrets-manager
helm uninstall external-secrets -n external-secrets
Ngoài ra, các bạn hãy xoá cụm EKS, Secret Manager, IAM Role + Policy để đỡ tốn chi phí và bỏ các resource không dùng nữa nha,
3. Tổng kết
Việc sử dụng External Secrets Operator không chỉ đơn thuần là một kỹ thuật triển khai, mà nó thể hiện tư duy về bảo mật hiện đại: Sự tách biệt hoàn toàn giữa lưu trữ và tiêu thụ. Không có khái niệm đúng hoàn toàn, mà mỗi kiến thức sẽ phù hợp tại 1 thời điểm cụ thể.
Hy vọng bài viết này giúp bạn tự tin hơn trong việc xây dựng các cụm EKS best practice. Mong các bạn ủng hộ những bài viết của mình, nếu các bạn muốn tìm hiểu thêm vấn đề gì, hãy cho mình thêm gợi í nha. Hẹn mọi người chiến K8s tiếp ở bài viết tiếp theo nha 
All rights reserved