Thay thế AWS credentials trong GitHub Secrets bằng OIDC — không còn long-lived key nào cả
Nếu bạn đang dùng GitHub Actions để deploy lên AWS, khả năng cao repo của bạn đang có một cặp secrets trông như thế này:
AWS_ACCESS_KEY_ID = AKIAIOSFODNN7EXAMPLE
AWS_SECRET_ACCESS_KEY = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
Mình cũng từng làm vậy. Nhưng sau một lần ngồi audit lại IAM, mình nhận ra đây là một rủi ro bảo mật thực sự — không phải lý thuyết. Bài này mình sẽ giải thích tại sao, và hướng dẫn thay thế hoàn toàn bằng OIDC để GitHub Actions có thể xác thực với AWS mà không cần lưu bất kỳ credential nào.
GitHub Secrets không tệ, nhưng credentials trong đó thì có vấn đề
GitHub Secrets được mã hóa, không hiển thị trong log — về mặt cơ chế lưu trữ, nó ổn. Vấn đề không nằm ở Secrets, mà nằm ở thứ bạn lưu vào đó.
AWS_ACCESS_KEY_ID và AWS_SECRET_ACCESS_KEY là long-lived credentials — chúng không hết hạn, không tự rotate, và tồn tại độc lập hoàn toàn với vòng đời của workflow. Điều đó có nghĩa là:
- Key vẫn còn hiệu lực dù repo bị archive, dù người tạo key đã nghỉ việc, dù bạn đã đổi cả pipeline.
- Nếu key bị lộ — qua một dependency độc hại, qua một PR từ fork, hay qua bất kỳ lỗ hổng nào — kẻ tấn công có thể dùng nó từ bất cứ đâu, không cần GitHub.
- Bạn phải chủ động rotate định kỳ. Trong thực tế, rất ít team làm điều này đều đặn.
Vấn đề căn bản là: bạn đang cấp một chiếc chìa khóa vĩnh viễn, rồi nhét nó vào một hệ thống CI/CD mà hàng chục người có thể đọc, hàng trăm workflow có thể chạy. Đó là bề mặt tấn công lớn hơn bạn nghĩ.
OIDC: Không còn chìa khóa nào để mất
OIDC — OpenID Connect — thay đổi hoàn toàn mô hình này. Thay vì bạn lưu credentials vào GitHub rồi truyền sang AWS, bạn thiết lập một quan hệ tin tưởng trực tiếp giữa hai bên:
"AWS ơi, nếu GitHub xác nhận rằng job này đang chạy từ repo X, branch Y — thì hãy cấp quyền cho nó, nhưng chỉ trong vài giờ thôi."
Khi workflow chạy, GitHub tạo một JWT token ngắn hạn, ký bằng private key của GitHub. AWS nhận token này, verify với GitHub's public OIDC endpoint, rồi trả về một bộ credentials tạm thời qua STS. Credentials đó tự hết hạn — bạn không cần làm gì thêm.
Không có secret nào được lưu trong GitHub Settings. Không có gì để rotate. Không có gì để lộ ra ngoài ngữ cảnh của một job đang chạy.
Setup: Phía AWS trước
Bước 1 — Khai báo GitHub là Identity Provider
Vào IAM → Identity Providers → Add Provider. Chọn OpenID Connect:
- Provider URL:
https://token.actions.githubusercontent.com - Audience:
sts.amazonaws.com
Hoặc CLI:
aws iam create-open-id-connect-provider \
--url https://token.actions.githubusercontent.com \
--client-id-list sts.amazonaws.com \
--thumbprint-list 6938fd4d98bab03faadb97b34396831e3780aea1
Bước 2 — Tạo IAM Role với Trust Policy
Đây là phần cốt lõi. Trust Policy định nghĩa chính xác ai được phép assume role này:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::YOUR_ACCOUNT_ID:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
},
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:your-org/your-repo:ref:refs/heads/main"
}
}
}
]
}
Phần sub trong Condition là chìa khóa — bạn có thể giới hạn đến từng branch, từng environment, hay từng repo. Chỉ job khớp điều kiện đó mới được assume role. Không khớp → bị từ chối, dù chạy từ cùng GitHub account.
Gắn Permission Policy phù hợp vào Role — chỉ đủ quyền để job làm việc của nó, không hơn.
Setup: Phía GitHub workflow
name: Deploy to AWS
on:
push:
branches: [main]
permissions:
id-token: write # Bắt buộc để GitHub tạo OIDC token
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::YOUR_ACCOUNT_ID:role/YOUR_ROLE_NAME
aws-region: ap-northeast-1
- name: Deploy
run: aws s3 sync ./dist s3://your-bucket --delete
Không có AWS_ACCESS_KEY_ID. Không có AWS_SECRET_ACCESS_KEY. Action configure-aws-credentials tự xử lý việc exchange token và inject credentials tạm thời cho các bước tiếp theo.
Sau khi migrate xong, bạn có thể — và nên — xóa hẳn hai secrets cũ trong GitHub Settings. Và revoke IAM User key đó luôn.
Mẹo: Dùng GitHub Environments để khóa chặt hơn
Nếu bạn có nhiều môi trường (staging, production), hãy tạo IAM Role riêng cho từng môi trường và dùng GitHub Environments để ràng buộc:
jobs:
deploy-prod:
environment: production
...
Trust Policy phía AWS:
"token.actions.githubusercontent.com:sub": "repo:your-org/your-repo:environment:production"
Bây giờ Role production chỉ có thể được assume khi job chạy đúng trong environment production — không phải bất kỳ branch hay workflow nào khác. Kết hợp với required reviewers trong GitHub Environments, bạn có một lớp kiểm soát khá chắc.
Kết
GitHub Secrets là công cụ tốt — nhưng không phải nơi phù hợp để lưu cloud credentials. OIDC cho phép bạn loại bỏ hoàn toàn long-lived credentials ra khỏi pipeline: không lưu, không rotate, không lo lộ.
Setup mất khoảng 30 phút lần đầu. Đổi lại, bạn không còn phải nhớ "hình như key AWS của repo này sắp hết hạn chưa" nữa.
GCP và Azure cũng hỗ trợ OIDC với GitHub Actions theo cách tương tự — concept giống hệt, chỉ khác phần cấu hình phía cloud.
Nếu repo của bạn hiện vẫn còn AWS credentials trong GitHub Settings, đây là thời điểm tốt để xem xét việc migrate. Phần khó nhất là lần đầu setup Trust Policy — sau đó mọi thứ khá thẳng thắn.
All rights reserved