Cảnh báo bảo mật lúc 2 giờ sáng
Tôi vẫn nhớ một buổi sáng vài năm trước khi điện thoại của mình bùng nổ với các cảnh báo. Một lập trình viên đã vô tình push một file .env chứa thông tin đăng nhập cơ sở dữ liệu production lên một repository GitHub công khai. Chỉ trong vòng 60 giây, các bot tự động đã thu thập được các key này và cố gắng khởi tạo các instance EC2 trái phép. Chúng tôi đã phải dành 6 giờ tiếp theo để xoay vòng (rotate) mọi thông tin xác thực và kiểm tra nhật ký truy cập để tìm dấu hiệu của một vụ xâm nhập.
Đây không phải là một câu chuyện kinh dị hiếm gặp. Chỉ riêng trong năm 2023, các nhà nghiên cứu đã tìm thấy hơn 10 triệu secret bị lộ trên các repository công khai. Nhiều nhóm bắt đầu bằng cách hardcode secret hoặc lưu trữ chúng dưới dạng văn bản thuần (plain text) trong các biến môi trường CI/CD. Mặc dù cách này hiệu quả cho một bản MVP nhanh chóng, nhưng nó tạo ra một lỗ hổng bảo mật khổng lồ. Việc chuyển từ một ‘scripter’ sang một kỹ sư DevOps chuyên nghiệp đòi hỏi phải làm chủ việc quản lý secret tập trung.
Tại sao tình trạng Secret Sprawl xảy ra
Secret sprawl (tình trạng phân tán secret) xảy ra khi các dữ liệu nhạy cảm—API key, mật khẩu cơ sở dữ liệu và SSH key—bị rải rác khắp GitHub, Jira, Slack và máy tính cá nhân của lập trình viên. Điều này hiếm khi xảy ra do sự lười biếng. Thông thường, đó là vì quy trình ‘chính thống’ để lấy một secret quá chậm. Nếu một lập trình viên phải đợi ba ngày để một ticket được phê duyệt nhằm lấy staging key, họ sẽ chỉ đơn giản là copy-paste nó từ đồng nghiệp.
Kubernetes Secrets tiêu chuẩn không giải quyết được vấn đề này. Theo mặc định, Kubernetes lưu trữ secret trong etcd dưới dạng chuỗi mã hóa Base64. Hãy làm rõ điều này: Base64 là kỹ thuật xáo trộn (obfuscation), không phải mã hóa (encryption). Bất kỳ ai có quyền get secret cơ bản trong một namespace đều có thể giải mã chúng chỉ trong vài giây.
# Giải mã một K8s secret không tốn chút công sức nào
echo "S3ViamVjdFBhc3N3b3Jk" | base64 --decode
Khi tôi đang debug các secret mặc định này, tôi thường sử dụng Base64 Encoder/Decoder từ ToolCraft để xác minh các giá trị một cách nhanh chóng. Nó chạy 100% trong trình duyệt. Điều này đảm bảo rằng các chuỗi nhạy cảm không bao giờ rời khỏi môi trường cục bộ của bạn, một thói quen bảo mật nhỏ nhưng quan trọng.
HashiCorp Vault so với AWS Secrets Manager
Khi bạn quyết định ngừng sử dụng các file văn bản thuần, bạn có thể sẽ chọn giữa một công cụ độc lập với nền tảng như HashiCorp Vault hoặc một dịch vụ cloud-native như AWS Secrets Manager.
HashiCorp Vault
Vault là tiêu chuẩn ngành trong việc quản lý secret. Nó cung cấp một cách tập trung để lưu trữ, truy cập và bảo vệ secret. Tính năng yêu thích của tôi là Dynamic Secrets. Thay vì cung cấp cho ứng dụng một mật khẩu PostgreSQL tĩnh và tồn tại lâu dài, Vault sẽ tạo ra một user tạm thời chỉ tồn tại trong 15 phút. Nếu ứng dụng bị xâm nhập, thông tin xác thực bị đánh cắp sẽ trở nên vô dụng gần như ngay lập tức.
- Ưu điểm: Hỗ trợ đa đám mây, các chính sách cực kỳ chi tiết và mã hóa hiệu suất cao dưới dạng dịch vụ (encryption as a service).
- Nhược điểm: Có lộ trình học tập khá dốc. Việc tự vận hành (self-hosting) phiên bản mã nguồn mở đòi hỏi nỗ lực vận hành đáng kể.
AWS Secrets Manager
Nếu toàn bộ stack của bạn nằm trên AWS, đây là lựa chọn mượt mà nhất. Nó tích hợp sẵn với IAM (Identity and Access Management) và đảm nhận công việc nặng nhọc là tự động xoay vòng mật khẩu cơ sở dữ liệu RDS.
- Ưu điểm: Không tốn công bảo trì và tích hợp sẵn với các dịch vụ AWS.
- Nhược điểm: Chi phí. Với mức giá 0,40 USD cho mỗi secret mỗi tháng, một kiến trúc microservices với hàng trăm secret có thể nhanh chóng làm tăng hóa đơn của bạn.
Inject Secret vào Kubernetes
Lưu trữ secret trong vault chỉ là một phần của bài toán. Thách thức thực sự là đưa những secret đó vào pod của bạn mà ứng dụng thậm chí không cần biết đến sự tồn tại của vault. Tôi thường đề xuất hai mô hình: Vault Agent Sidecar Injector hoặc External Secrets Operator (ESO).
Mô hình Sidecar Injector
Bạn thêm các annotation cụ thể vào Kubernetes Deployment của mình. Sau đó, Vault sẽ inject một sidecar container để lấy secret và ghi nó vào một volume bộ nhớ dùng chung tại /vault/secrets. Ứng dụng của bạn chỉ cần đọc secret như thể đó là một file cục bộ. Cách này rất tuyệt vời cho các ứng dụng cũ (legacy) không dễ dàng tích hợp với API.
apiVersion: apps/v1
kind: Deployment
metadata:
name: payment-service
annotations:
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/role: "payment-role"
vault.hashicorp.com/agent-inject-secret-config: "secrets/data/payment/config"
spec:
template:
spec:
containers:
- name: app
image: payment-service:v2.1.0
External Secrets Operator (ESO)
Tôi ưu tiên sử dụng ESO khi muốn tiếp tục dùng các đối tượng Kubernetes Secret tiêu chuẩn nhưng muốn chúng được đồng bộ hóa từ một nguồn bên ngoài. Nếu bạn cập nhật một giá trị trong AWS Secrets Manager, ESO sẽ tự động đẩy thay đổi đó vào cụm K8s của bạn. Đây là một cách tiếp cận gọn gàng hơn cho các nhóm muốn duy trì phong cách ‘K8s native’.
Quy trình CI/CD bảo mật
Trước khi viết bất kỳ dòng YAML nào cho một pipeline mới, tôi thường tạo các thông tin xác thực có độ hỗn loạn (entropy) cao. Tôi sử dụng Password Generator trên ToolCraft để tạo các chuỗi 32 ký tự có khả năng chống lại các cuộc tấn công brute-force. Khi kiểm tra API payload cho Vault, tôi cũng sử dụng YAML to JSON Converter của họ để đảm bảo cấu hình của mình hợp lệ trước khi triển khai.
1. Xác thực bằng OIDC
Đừng bao giờ lưu trữ các IAM key tồn tại lâu dài trong GitHub Secrets. Thay vào đó, hãy sử dụng OIDC (OpenID Connect). Điều này cho phép GitHub Actions yêu cầu một token tạm thời, ngắn hạn từ AWS dựa trên một mối quan hệ tin cậy. Không có key nào được lưu trữ, vì vậy hacker sẽ không có gì để đánh cắp từ cài đặt CI của bạn.
2. Sử dụng Runtime Fetching
Thay vì inject mật khẩu cơ sở dữ liệu thực tế vào môi trường build, hãy lưu trữ một SECRET_ID. Pipeline sử dụng ID đó để lấy giá trị thực từ secret manager của bạn chỉ khi thực sự cần thiết cho một bước triển khai.
# Ví dụ GitHub Action sử dụng OIDC
- name: Lấy Production Secrets
uses: aws-actions/aws-secretsmanager-get-secrets@v2
with:
secret-ids: |
DB_PASSWORD, prod/billing/db-pass
3. Kiểm tra (Audit) mọi thứ
Triển khai mới chỉ là bước đầu tiên. Bạn phải bật tính năng logging cho tất cả các hoạt động truy cập secret. Nếu một kỹ sư truy cập vào một thông tin xác thực production, phải có một dấu vết không thể thay đổi (immutable trail). Cả Vault và AWS đều cung cấp nhật ký chi tiết cho bạn biết chính xác ai đã truy cập vào cái gì và khi nào.
Lời kết
Chuyển sang một hệ thống quản lý secret bài bản ban đầu có vẻ như tốn nhiều công sức. Tuy nhiên, tính bảo mật và khả năng mở rộng mà nó mang lại hoàn toàn xứng đáng với những khó khăn ban đầu đó. Bạn sẽ không còn phải lo lắng về việc rò rỉ vô tình và có thể tập trung vào việc phát hành tính năng. Nếu bạn mới bắt đầu, hãy chọn mật khẩu cơ sở dữ liệu nhạy cảm nhất của mình và chuyển nó vào một trình quản lý ngay hôm nay. Những bước nhỏ sẽ dẫn đến một hạ tầng kiên cố.

