Tài khoản AWS & IAM cơ bản: root, user, role, policy, MFA, CLI, Budget
Tiếp đi: AWS thực hành. Mình vừa tạo tài khoản AWS để đưa shop-app lên. Hiện mình đăng nhập bằng email root, và để chạy aws CLI mình đã tạo access key cho root rồi dán vào ~/.aws/credentials. Làm vậy có sao không?
Có sao đấy, và đây là lỗi phổ biến nhất của người mới dùng AWS. Ở bài 18.5 ta đã có bản đồ dịch vụ; chương này đi từng mảnh để đưa shop-app lên AWS thật. Mảnh đầu tiên không phải EC2 hay RDS, mà là ai được làm gì trong tài khoản: IAM. Làm sai từ đây thì mọi thứ phía sau đều có lỗ hổng.
1. Bài toán: một tài khoản, một con người, nhiều "danh tính"
Bạn đang có:
Tài khoản AWS (Account ID: 123456789012)
└── root user = email đăng ký + password
├── đăng nhập Console bằng root
└── access key của root nằm trong ~/.aws/credentials ← nguy hiểm
Mục tiêu của chương: shop-app chạy trên EC2/ECS, đọc ảnh từ S3, nối RDS, gửi SQS. Mỗi thành phần đó đều cần quyền. Ta sẽ phải trả lời ba câu hỏi lặp lại suốt chương:
- Con người (bạn, đồng đội) vào AWS bằng gì?
- Chương trình (shop-app, CLI, CI) gọi API AWS bằng gì?
- Ai có quyền gì, và làm sao chứng minh được điều đó khi audit?
2. Vấn đề: dùng root cho mọi việc
Root user là danh tính không thể bị giới hạn. Không có policy nào gắn vào root để bớt quyền được. Hệ quả:
- Access key của root lọt ra (push nhầm lên Git, lộ trong log) → kẻ tấn công có toàn quyền: tạo EC2 đào coin, xóa backup, đổi email tài khoản. Không có "lớp" nào chặn.
- Không có MFA → chỉ cần password là vào được.
- Không tách được ai làm gì: CloudTrail ghi "root" cho mọi thao tác, không biết là bạn hay đồng đội.
- Không có hạn mức chi tiêu → tháng sau nhận hóa đơn mới biết quên tắt NAT Gateway.
Thuật ngữ đề thi đi kèm ở đây: least privilege (quyền tối thiểu), temporary credentials (credential tạm), MFA, separation of duties.
3. Giải pháp: mô hình IAM chuẩn cho tài khoản lab
IAM (Identity and Access Management) là dịch vụ global (không theo region), miễn phí, trả lời câu "principal nào được gọi action nào trên resource nào". Các khái niệm cần nắm:
| Khái niệm | Là gì | Dùng cho |
|---|---|---|
| Root user | Chủ tài khoản, toàn quyền, không giới hạn được | Chỉ việc buộc phải root: đổi plan hỗ trợ, đóng tài khoản, vài setting billing. Bật MFA, không tạo access key |
| IAM user | Danh tính dài hạn: password (Console) và/hoặc access key (API/CLI) | Con người hoặc máy ngoài AWS. Nên ít user, mỗi người một user, có MFA |
| IAM group | Tập hợp user để gắn policy chung | Gom quyền theo vai (developers, admins). Group không lồng nhau |
| IAM role | Danh tính không có credential cố định; ai được assume nó sẽ nhận credential tạm (STS) | EC2, Lambda, ECS task, cross-account, con người nâng quyền tạm thời |
| Policy | Tài liệu JSON: Effect / Action / Resource / Condition | Gắn vào user, group, role (identity-based) hoặc vào resource như S3 bucket (resource-based) |
| STS | Security Token Service: phát credential tạm (access key + secret + session token, có hạn 15 phút–12 giờ) | Đứng sau mọi lần assume role |
| MFA | Yếu tố thứ hai: app TOTP, FIDO2 key, passkey | Bắt buộc cho root và mọi user có password |
Mô hình ta dựng cho tài khoản lab của shop-app:
Account 123456789012 (alias: shop-lab)
│
├── root ── MFA bật, không access key, cất password đi
│
├── IAM user shop-admin
│ ├── password + MFA (virtual)
│ ├── 1 access key (dùng cho CLI ở máy bạn)
│ └── policy rất hẹp: chỉ được sts:AssumeRole → ShopAdminRole
│ + tự quản MFA / access key của mình
│
├── IAM role ShopAdminRole
│ ├── trust policy: chỉ shop-admin, và phải có MFA
│ ├── permission: AdministratorAccess (tạm cho lab)
│ └── max session: 1 giờ
│
├── IAM role shop-app-ec2-role (bài 22.4: EC2 đọc S3, ghi log)
├── IAM role shop-lambda-role (bài 22.12)
│
└── AWS Budgets shop-lab-monthly: báo email khi vượt 80% của 10 USD
Ý tưởng chính: user chỉ là chiếc chìa khóa để mở cửa role. Mọi quyền thật nằm ở role, và role chỉ cấp credential sống 1 giờ. Access key của shop-admin lọt ra ngoài thì kẻ tấn công vẫn không làm gì được nếu không có MFA code.
4. Cấu hình từng bước
Bước 1: Policy JSON, đọc cho quen. Mọi policy đều có dạng này:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowReadProductImages",
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:ListBucket"],
"Resource": [
"arn:aws:s3:::shop-app-assets-123456789012",
"arn:aws:s3:::shop-app-assets-123456789012/*"
],
"Condition": {
"StringEquals": { "aws:RequestedRegion": "ap-southeast-1" }
}
}
]
}
Version: luôn là2012-10-17(bản cũ2008-10-17không hỗ trợ biến như${aws:username}).Effect:AllowhoặcDeny. Mặc định là implicit deny: không nói gì nghĩa là không được.Action:service:Operation, chấp nhận wildcard (s3:Get*).Resource: ARN. Chú ý S3: bucket và object là hai ARN khác nhau (bucketvàbucket/*).Condition: điều kiện thêm (region, IP, MFA, tag…). Bài 22.2 đi sâu.
Policy tồn tại ở ba dạng, đề thi và thực tế đều phân biệt:
| Dạng | Ai tạo / ai sửa | Gắn được vào | Dùng khi |
|---|---|---|---|
AWS managed (arn:aws:iam::aws:policy/...) | AWS tạo và cập nhật khi có API mới | Nhiều user/group/role, nhiều account | Bắt đầu nhanh: AdministratorAccess, ReadOnlyAccess, AmazonSSMManagedInstanceCore. Thường rộng hơn cần |
Customer managed (arn:aws:iam::123456789012:policy/...) | Bạn tạo, có version (giữ 5 bản, rollback được) | Nhiều user/group/role trong account | Quyền chuẩn của team: ShopAdminUserPolicy, ShopAppS3ReadPolicy. Nên là mặc định |
| Inline | Nhúng thẳng vào một user/group/role, không có ARN riêng | Đúng một principal | Quan hệ 1-1 chặt, xóa principal là policy mất theo. Khó tái sử dụng, khó audit |
Giới hạn hay bị hỏi: mỗi user/role gắn tối đa 10 managed policy (có thể xin tăng lên 20); kích thước policy identity-based tối đa 2048 ký tự cho user, 6144 cho role; trust policy tối đa 2048.
Bước 2: ARN, cách đặt tên mọi thứ trên AWS.
arn:partition:service:region:account-id:resource
arn:aws:iam::123456789012:user/shop-admin ← IAM global: region để trống
arn:aws:iam::123456789012:role/ShopAdminRole
arn:aws:s3:::shop-app-assets-123456789012 ← S3: cả region và account trống
arn:aws:ec2:ap-southeast-1:123456789012:instance/i-0abc123
arn:aws:sqs:ap-southeast-1:123456789012:shop-order-events
Bước 3: Trust policy của role. Role có hai policy: trust policy (ai được assume) và permission policy (assume xong được làm gì). Thiếu một trong hai là không dùng được.
// Trust policy của ShopAdminRole
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::123456789012:user/shop-admin" },
"Action": "sts:AssumeRole",
"Condition": { "Bool": { "aws:MultiFactorAuthPresent": "true" } }
}]
}
Với role cho dịch vụ (EC2, Lambda), Principal là service: {"Service": "ec2.amazonaws.com"}. Đây là điểm đề thi hay hỏi: service-linked role và service role đều là role mà principal là một dịch vụ AWS.
Bước 4: CLI profile. AWS CLI v2 đọc hai file:
# ~/.aws/credentials (chỉ chứa key dài hạn, chmod 600)
[shop-admin]
aws_access_key_id = AKIA................
aws_secret_access_key = ....................................
# ~/.aws/config
[profile shop-admin]
region = ap-southeast-1
output = json
[profile shop-lab] ← profile dùng cho cả chương
role_arn = arn:aws:iam::123456789012:role/ShopAdminRole
source_profile = shop-admin
mfa_serial = arn:aws:iam::123456789012:mfa/shop-admin
duration_seconds = 3600
region = ap-southeast-1
output = json
Khi chạy aws s3 ls --profile shop-lab, CLI tự: lấy key của shop-admin → hỏi MFA code → gọi sts:AssumeRole → cache credential tạm 1 giờ trong ~/.aws/cli/cache → gọi S3. Bạn không bao giờ phải copy session token bằng tay.
Bước 5: Java SDK v2 dùng cùng profile. Code không đổi giữa local và production nhờ default credentials provider chain (xem lại bài 18.5). Ở local, chỉ cần đặt biến môi trường:
export AWS_PROFILE=shop-lab
export AWS_REGION=ap-southeast-1
java -jar shop-app.jar
// Kiểm tra app đang chạy với danh tính nào. SDK v2: software.amazon.awssdk:sts
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.sts.StsClient;
import software.amazon.awssdk.services.sts.model.GetCallerIdentityResponse;
@Component
public class AwsIdentityLogger {
private static final Logger log = LoggerFactory.getLogger(AwsIdentityLogger.class);
@EventListener(ApplicationReadyEvent.class)
public void logIdentity() {
try (StsClient sts = StsClient.builder()
.region(Region.AP_SOUTHEAST_1)
.build()) { // không truyền credential: dùng chain
GetCallerIdentityResponse me = sts.getCallerIdentity();
log.info("AWS identity: account={} arn={}", me.account(), me.arn());
// local : arn:aws:sts::123456789012:assumed-role/ShopAdminRole/botocore-session-...
// EC2 : arn:aws:sts::123456789012:assumed-role/shop-app-ec2-role/i-0abc123
}
}
}
Với Spring Cloud AWS 3 (awspring), cấu hình tương đương trong application.yml (chỉ dùng cho local profile, production để trống cho chain tự lo):
spring:
cloud:
aws:
region:
static: ${AWS_REGION:ap-southeast-1}
credentials:
profile:
name: ${AWS_PROFILE:} # trống trên EC2/ECS → dùng role của máy
Thứ tự chain của SDK v2 (nhớ để debug): Java system property → biến môi trường (AWS_ACCESS_KEY_ID…) → Web identity token (IRSA) → profile trong ~/.aws → ECS container credentials → EC2 instance metadata (IMDS).
Bước 6: AWS Budgets. Không phải IAM, nhưng là việc bắt buộc ngày đầu. Budget là ngưỡng chi tiêu bạn đặt (ví dụ 10 USD/tháng); vượt 80% thực tế hoặc dự báo vượt 100% thì gửi email/SNS. Budget không tự tắt tài nguyên (trừ khi cấu hình Budget Actions), nó chỉ báo. Lab bên dưới tạo bằng CLI.
5. Cách hoạt động: một lệnh CLI đi qua những gì
aws s3 ls --profile shop-lab
│
│ 1. đọc ~/.aws/config → profile shop-lab cần assume role
│ 2. lấy key dài hạn của source_profile shop-admin
│ 3. hỏi "Enter MFA code for arn:aws:iam::...:mfa/shop-admin:"
▼
STS (sts.ap-southeast-1.amazonaws.com)
│ AssumeRole(RoleArn=ShopAdminRole, SerialNumber=mfa/shop-admin, TokenCode=123456)
│ ├─ kiểm tra policy của shop-admin: có sts:AssumeRole lên role này? ✔
│ ├─ kiểm tra trust policy của role: principal = shop-admin? MFA present? ✔
│ └─ phát credential tạm: AccessKeyId ASIA..., SecretAccessKey, SessionToken, Expiration (+1h)
▼
S3 API (gửi kèm SessionToken, ký SigV4)
│ IAM kiểm tra permission policy của ShopAdminRole: s3:ListAllMyBuckets? ✔
▼
2025-01-10 shop-app-assets-123456789012
2025-01-10 shop-app-logs-123456789012
Nhận ra ngay hai dấu hiệu: key dài hạn bắt đầu bằng AKIA, key tạm từ STS bắt đầu bằng ASIA. Thấy AKIA trong log hay trong code là có chuyện.
Mọi lời gọi API (kể cả từ Console) đều được CloudTrail ghi lại kèm danh tính thật: assumed-role/ShopAdminRole/botocore-session-1700000000. Đó là lý do role tốt hơn user chung: audit biết ai, lúc nào, qua role nào (CloudTrail đi sâu ở bài 22.15).
6. So sánh: user vs role vs access key vs Identity Center
| Tiêu chí | IAM user + access key | IAM role (assume) | IAM Identity Center (SSO) |
|---|---|---|---|
| Credential | Dài hạn, không hết hạn | Tạm (15 phút – 12 giờ) | Tạm, lấy qua trình duyệt (aws sso login) |
| Rủi ro lộ key | Cao: lộ là dùng được tới khi bị xóa | Thấp: hết hạn tự vô hiệu | Thấp |
| Dùng cho | Máy ngoài AWS không có cách khác, tài khoản lab nhỏ | EC2/ECS/Lambda, cross-account, con người nâng quyền | Con người ở tổ chức nhiều account |
| Rotation | Phải tự xoay (khuyến nghị ≤ 90 ngày) | Tự động | Tự động |
| Đề thi gợi ý | Thường là đáp án sai ("store access keys in the application") | Đáp án đúng cho "application on EC2 needs to access S3" | Đáp án đúng cho "centralized access, multiple accounts, existing Active Directory" |
Tài khoản lab cá nhân thì dùng user + role như bài này là đủ. Vào công ty, phần lớn sẽ đăng nhập qua Identity Center (bài 22.20), nhưng cơ chế bên dưới vẫn là assume role → credential tạm.
7. Khi nào dùng / không dùng
- Root: chỉ 5–6 việc AWS liệt kê "require root" (đóng tài khoản, đổi support plan, khôi phục quyền S3 bucket bị lock…). Còn lại: không.
- IAM user có access key: khi không có lựa chọn khác (CI chạy ngoài AWS chưa dùng OIDC, máy on-prem). Luôn kèm MFA cho Console, rotation cho key, và tốt nhất chỉ dùng để assume role.
- Role: mọi workload chạy trong AWS (EC2 instance profile, ECS task role, Lambda execution role), mọi truy cập cross-account, GitHub Actions qua OIDC.
- Không: tạo một user "shop-app" rồi nhúng key vào
application.ymlcủa shop-app. Đây là lỗi mà bài 22.4 sẽ sửa bằng instance profile.
Mình chưa thấy rõ lợi ích. Nếu mình gắn thẳng AdministratorAccess cho user shop-admin thì xong, sao phải thêm một role ShopAdminRole rồi assume vào cho rắc rối? User và role khác nhau bản chất chỗ nào?
Bạn hiểu đúng là về quyền hai cách cho kết quả giống nhau: gọi API đều được. Khác nhau nằm ở credential và vòng đời của nó.
1. Bản chất
IAM user = danh tính + credential dài hạn gắn chặt (password, access key)
IAM role = danh tính + KHÔNG có credential; mỗi lần assume → STS phát credential mới, có hạn
Hình dung: user là chiếc thẻ nhân viên bạn đeo suốt ngày; role là chiếc thẻ "phòng server" bạn phải xin ở quầy bảo vệ, dùng 1 giờ rồi tự hủy. Mất thẻ nhân viên thì kẻ gian vẫn phải qua quầy bảo vệ (MFA) mới vào phòng server.
2. Ba lợi ích cụ thể
- Blast radius khi lộ key. Key của shop-admin lộ: kẻ tấn công chỉ có quyền
sts:AssumeRole, mà trust policy đòi MFA → không làm gì được. Nếu gắn AdministratorAccess thẳng vào user, lộ key là mất tài khoản. - Một cơ chế cho mọi thứ. EC2 không có password, Lambda không có access key: chúng chỉ biết assume role. Con người cũng dùng role thì cách phân quyền, audit, point of control giống nhau. Vào công ty, bạn sẽ thấy
DeveloperRole,ReadOnlyRole,BreakGlassAdminRole; người ta chuyển vai bằng cách đổi role, không đổi user. - Cross-account. Role là cách duy nhất để account B dùng tài nguyên account A mà không tạo user ở A. Bài 22.2 làm lab này.
3. Chi phí của sự "rắc rối"
Thực tế là một dòng --profile shop-lab và nhập MFA mỗi giờ một lần. CLI cache token, SDK tự refresh. Console cũng có nút Switch role, bạn đăng nhập shop-admin rồi chuyển sang ShopAdminRole, thanh menu đổi màu để nhắc bạn đang ở vai nào.
4. Khi nào gắn policy thẳng vào user là chấp nhận được
Policy "tự phục vụ" rất hẹp: đổi password của mình, bật MFA cho mình, xoay access key của mình, và sts:AssumeRole. Đúng như lab bên dưới làm. Quyền thật thì để ở role.
Mình chạy aws s3 ls thì bị AccessDenied dù vừa gắn policy cho user. Chạy lệnh khác lại báo InvalidClientTokenId. Làm sao biết CLI đang dùng credential nào, và gỡ hai lỗi này ra sao?
Hai lỗi này khác nhau hẳn, và lệnh đầu tiên cần gõ khi gặp bất kỳ lỗi quyền nào trên AWS là:
aws sts get-caller-identity --profile shop-lab
{
"UserId": "AROA...:botocore-session-1700000000",
"Account": "123456789012",
"Arn": "arn:aws:sts::123456789012:assumed-role/ShopAdminRole/botocore-session-1700000000"
}
Nó trả về bạn đang là ai. Rất nhiều lỗi "policy đúng mà vẫn denied" chỉ vì CLI đang dùng một credential khác với cái bạn nghĩ.
1. CLI chọn credential theo thứ tự nào
1. tham số dòng lệnh --profile, --region
2. biến môi trường AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY / AWS_SESSION_TOKEN
AWS_PROFILE, AWS_DEFAULT_REGION
3. ~/.aws/credentials [default] hoặc [profile-name]
4. ~/.aws/config [profile xxx] (role_arn, sso_*, credential_process…)
5. container credentials (ECS)
6. instance metadata (EC2 IMDS)
Bẫy hay gặp: bạn từng export AWS_ACCESS_KEY_ID=... trong shell cũ. Biến môi trường đứng trên file profile, nên --profile bị lờ đi với lệnh không truyền profile. Kiểm tra bằng env | grep AWS_ và aws configure list --profile shop-lab: cột Type cho biết credential lấy từ đâu (env, shared-credentials-file, assume-role).
2. InvalidClientTokenId / SignatureDoesNotMatch
Nghĩa là chưa tới bước kiểm tra quyền: AWS không nhận ra access key. Nguyên nhân: key gõ sai, key đã bị xóa/deactivate, dán thừa khoảng trắng, hoặc dùng key của tài khoản khác. Đồng hồ máy sai quá 5 phút cũng gây lỗi chữ ký (SigV4 có timestamp). Cách sửa: aws configure --profile shop-admin nhập lại key, kiểm tra aws iam list-access-keys --user-name shop-admin xem key có Active.
3. AccessDenied
Credential hợp lệ, nhưng policy không cho. Thông điệp lỗi của AWS ngày càng cụ thể, hãy đọc hết:
An error occurred (AccessDenied) when calling the ListBuckets operation:
User: arn:aws:iam::123456789012:user/shop-admin is not authorized to perform:
s3:ListAllMyBuckets on resource: arn:aws:s3:::* because no identity-based policy
allows the s3:ListAllMyBuckets action
Đọc được bốn thứ: ai (user shop-admin, không phải role: bạn quên --profile shop-lab), action (s3:ListAllMyBuckets, không phải s3:ListBucket như bạn tưởng), resource, và lý do ("no identity-based policy allows" khác với "explicit deny in a service control policy"). Checklist:
get-caller-identity: đúng danh tính chưa?- Action đúng tên chưa?
s3 lskhông bucket =s3:ListAllMyBuckets;s3 ls s3://bucket=s3:ListBucket; tải file =s3:GetObject. - Resource ARN đúng chưa? Bucket vs
bucket/*. - Có Deny ở đâu đó không (SCP, permission boundary, bucket policy, điều kiện MFA)? Bài 22.2.
- Policy vừa gắn có thể cần vài giây để lan (IAM eventually consistent).
Thêm --debug vào lệnh để thấy CLI chọn profile nào và ký request bằng key nào (che bớt khi chia sẻ log).
Thứ mình sợ nhất là làm lab xong quên xóa, cuối tháng bị tính tiền. Free Tier che được những gì, và có cách nào AWS báo trước khi vượt ngân sách không?
Đúng là nên sợ, và cũng đúng là có cách phòng. Ba lớp: biết Free Tier che gì, đặt Budget, và tag mọi thứ để dọn được.
1. Free Tier có ba kiểu
| Kiểu | Ví dụ | Lưu ý |
|---|---|---|
| 12 tháng đầu (tài khoản mới) | EC2 t2/t3.micro 750 giờ/tháng, RDS db.t3.micro 750 giờ, S3 5 GB, EBS 30 GB | 750 giờ ≈ 1 instance chạy liên tục. Chạy 2 instance là tính tiền nửa tháng |
| Luôn miễn phí | Lambda 1 triệu request/tháng, DynamoDB 25 GB, SQS 1 triệu request, CloudWatch 10 metric tùy chỉnh, IAM, Budgets (2 budget đầu) | Đây là lý do serverless hợp với lab |
| Trial ngắn | Một số dịch vụ 30–90 ngày đầu | Kiểm tra trang Free Tier trước khi tin |
Những thứ không bao giờ nằm trong Free Tier mà lab chương này sẽ chạm: NAT Gateway (tính theo giờ + GB, dễ tốn nhất), ALB, Elastic IP không gắn vào instance đang chạy, RDS Multi-AZ, ElastiCache, Fargate, Kinesis, EBS snapshot vượt hạn mức. Mỗi lab sẽ ghi rõ cái nào tốn tiền và lệnh xóa.
Lưu ý: AWS đang chuyển dần tài khoản mới sang mô hình Free Tier theo credit (có hạn mức tiền và thời gian) thay cho 12 tháng. Khi tạo tài khoản, đọc đúng phiên bản bạn được áp dụng, đừng nhớ theo tài liệu cũ.
2. AWS Budgets vs các công cụ khác
| Công cụ | Làm gì | Khi nào |
|---|---|---|
| AWS Budgets | Đặt ngưỡng (cost, usage, RI/SP coverage); báo khi thực tế hoặc dự báo vượt; có thể gắn Budget Action (gắn policy deny, stop EC2/RDS) | Phòng ngừa: tạo ngay ngày đầu |
| Cost Explorer | Biểu đồ, lọc theo service/tag/region, dự báo | Phân tích: "tiền đi đâu tháng này" |
| Cost Anomaly Detection | Machine learning phát hiện chi tiêu bất thường | Phát hiện đột biến, miễn phí |
| CloudWatch billing alarm | Alarm trên metric EstimatedCharges (chỉ ở us-east-1) | Cách cũ, Budgets thay được |
| Free Tier alerts | Email khi dùng tới 85% hạn mức Free Tier | Bật trong Billing preferences |
Đề thi: "notify when forecasted cost exceeds the monthly budget" → AWS Budgets. "Analyze historical spend by service" → Cost Explorer. "Detect unusual spend automatically" → Cost Anomaly Detection. Bài 22.19 bàn kỹ hơn.
3. Tag để dọn
Mọi tài nguyên trong chương gắn Project=shop-app, Env=lab. Lợi ích: lọc Cost Explorer theo tag (phải bật cost allocation tag trong Billing), và tìm những gì còn sót để xóa:
aws resourcegroupstaggingapi get-resources \
--tag-filters Key=Project,Values=shop-app \
--query 'ResourceTagMappingList[].ResourceARN' --output table \
--profile shop-lab
Lệnh này là "bản kiểm kê" cuối mỗi lab. Danh sách rỗng nghĩa là dọn xong.
Cho mình bài lab để tự tay làm phần này nhé: dựng tài khoản lab với user, role, MFA, CLI profile và Budget đúng như sơ đồ.
Lab 22.1: Tài khoản lab shop-app, IAM user + role + MFA + CLI + Budget
Đây là lab nền: mọi lab sau đều dùng profile shop-lab tạo ở đây. Làm chậm, đọc output từng bước.
Mục tiêu
- Root user có MFA, không có access key; tài khoản có alias.
- IAM user
shop-admincó MFA, 1 access key, policy chỉ cho assume role và tự quản MFA/key. - Role
ShopAdminRoletrust shop-admin với điều kiện MFA;aws sts get-caller-identity --profile shop-labtrả về ARN dạngassumed-role/ShopAdminRole/.... - Budget
shop-lab-monthly10 USD báo email ở 80% thực tế và 100% dự báo.
Chi phí & thời gian
- IAM, STS, Budgets (2 budget đầu): miễn phí. Lab này không tạo tài nguyên tính tiền.
- Thời gian: 40–60 phút, chủ yếu ở Console cho phần MFA.
Chuẩn bị
- Một tài khoản AWS mới, tách khỏi tài khoản công ty hay tài khoản thật. Dùng email riêng (mẹo: Gmail cho phép
ban+awslab@gmail.com). Cần thẻ thanh toán để đăng ký. - App TOTP trên điện thoại (Google Authenticator, Authy, 1Password…) hoặc khóa FIDO2.
- AWS CLI v2:
aws --version→aws-cli/2.x. Nếu chưa có, cài theo trang hướng dẫn của AWS cho OS của bạn. - Trình
jq(tùy chọn, để đọc JSON).
Các bước
Bước 1: Khóa root. Đăng nhập Console bằng root.
- Console: góc phải tên tài khoản → Security credentials → Multi-factor authentication (MFA) → Assign MFA device → chọn Authenticator app → quét QR, nhập 2 mã liên tiếp.
- Cùng trang, mục Access keys: nếu có key nào, bấm Deactivate rồi Delete. Root không được có access key.
- Console: IAM → Dashboard → Account Alias → Create: đặt
shop-lab-<tên bạn>. Sau này đăng nhập ởhttps://shop-lab-xxx.signin.aws.amazon.com/console. - Console: Billing and Cost Management → Billing preferences: bật Receive Free Tier usage alerts và Receive CloudWatch billing alerts; ở Account → IAM user and role access to Billing information → Activate (để role xem được billing).
Bước 2: Tạo user bootstrap shop-admin (Console, vì chưa có CLI).
- Console: IAM → Users → Create user: tên
shop-admin, tick Provide user access to the AWS Management Console, chọn I want to create an IAM user, Custom password, tick must create new password. Permissions: Attach policies directly → AdministratorAccess (tạm, bước 6 sẽ gỡ). Tag:Project=shop-app,Env=lab. - Vào user vừa tạo → Security credentials → Create access key → chọn Command Line Interface → tải CSV, đóng tab. Đây là lần duy nhất secret hiện ra.
- Đăng xuất root. Đăng nhập lại bằng shop-admin, đổi password, rồi vào Security credentials → Assign MFA device cho chính user này. Ghi lại ARN MFA:
arn:aws:iam::<ACCOUNT_ID>:mfa/shop-admin.
Bước 3: Cấu hình CLI profile shop-admin.
aws configure --profile shop-admin
# AWS Access Key ID: AKIA... (từ CSV)
# AWS Secret Access Key: ....
# Default region name: ap-southeast-1
# Default output format: json
export AWS_REGION=ap-southeast-1
export ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text --profile shop-admin)
echo $ACCOUNT_ID # 12 chữ số
aws iam get-user --profile shop-admin
# "Arn": "arn:aws:iam::123456789012:user/shop-admin"
Console: không có; đây là bước ở máy bạn. Kiểm tra quyền file: chmod 600 ~/.aws/credentials.
Bước 4: Tạo role ShopAdminRole bằng CLI.
cat > trust-shop-admin-role.json <<EOF
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::${ACCOUNT_ID}:user/shop-admin" },
"Action": "sts:AssumeRole",
"Condition": { "Bool": { "aws:MultiFactorAuthPresent": "true" } }
}]
}
EOF
aws iam create-role \
--role-name ShopAdminRole \
--assume-role-policy-document file://trust-shop-admin-role.json \
--max-session-duration 3600 \
--description "Admin role for shop-app lab, MFA required" \
--tags Key=Project,Value=shop-app Key=Env,Value=lab \
--profile shop-admin
aws iam attach-role-policy \
--role-name ShopAdminRole \
--policy-arn arn:aws:iam::aws:policy/AdministratorAccess \
--profile shop-admin
Console: IAM → Roles → Create role → Custom trust policy (dán JSON) → Add permissions: AdministratorAccess → Name: ShopAdminRole. Để ý arn:aws:iam::aws:policy/...: account là chữ aws, tức AWS managed policy.
Bước 5: Policy hẹp cho shop-admin.
cat > shop-admin-user-policy.json <<'EOF'
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AssumeAdminRole",
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": "arn:aws:iam::ACCOUNT_ID:role/ShopAdminRole"
},
{
"Sid": "ManageOwnPasswordKeysAndMfa",
"Effect": "Allow",
"Action": [
"iam:GetUser", "iam:ChangePassword", "iam:GetAccountPasswordPolicy",
"iam:ListAccessKeys", "iam:CreateAccessKey", "iam:UpdateAccessKey", "iam:DeleteAccessKey",
"iam:ListMFADevices", "iam:CreateVirtualMFADevice", "iam:EnableMFADevice",
"iam:ResyncMFADevice", "iam:DeactivateMFADevice", "iam:DeleteVirtualMFADevice"
],
"Resource": [
"arn:aws:iam::ACCOUNT_ID:user/${aws:username}",
"arn:aws:iam::ACCOUNT_ID:mfa/${aws:username}"
]
},
{
"Sid": "WhoAmI",
"Effect": "Allow",
"Action": ["sts:GetCallerIdentity", "iam:ListAccountAliases"],
"Resource": "*"
}
]
}
EOF
sed -i "s/ACCOUNT_ID/${ACCOUNT_ID}/g" shop-admin-user-policy.json
# macOS: sed -i '' "s/ACCOUNT_ID/${ACCOUNT_ID}/g" shop-admin-user-policy.json
aws iam create-policy \
--policy-name ShopAdminUserPolicy \
--policy-document file://shop-admin-user-policy.json \
--tags Key=Project,Value=shop-app Key=Env,Value=lab \
--profile shop-admin
aws iam attach-user-policy \
--user-name shop-admin \
--policy-arn arn:aws:iam::${ACCOUNT_ID}:policy/ShopAdminUserPolicy \
--profile shop-admin
Để ý heredoc dùng 'EOF' (có nháy) để shell không phá ${aws:username}; ACCOUNT_ID thay bằng sed sau. Console: IAM → Policies → Create policy → JSON, rồi Users → shop-admin → Add permissions.
Bước 6: Cấu hình profile shop-lab và kiểm tra assume role trước khi gỡ quyền admin.
cat >> ~/.aws/config <<EOF
[profile shop-lab]
role_arn = arn:aws:iam::${ACCOUNT_ID}:role/ShopAdminRole
source_profile = shop-admin
mfa_serial = arn:aws:iam::${ACCOUNT_ID}:mfa/shop-admin
duration_seconds = 3600
region = ap-southeast-1
output = json
EOF
aws sts get-caller-identity --profile shop-lab
# Enter MFA code for arn:aws:iam::123456789012:mfa/shop-admin: 123456
# "Arn": "arn:aws:sts::123456789012:assumed-role/ShopAdminRole/botocore-session-17..."
Thấy assumed-role/ShopAdminRole là role hoạt động. Chỉ khi đó mới gỡ AdministratorAccess khỏi user:
aws iam detach-user-policy \
--user-name shop-admin \
--policy-arn arn:aws:iam::aws:policy/AdministratorAccess \
--profile shop-lab
# user giờ chỉ còn ShopAdminUserPolicy
aws iam list-attached-user-policies --user-name shop-admin --profile shop-lab
Nếu lỡ gỡ trước khi role chạy được, bạn vẫn cứu được bằng root (đó là việc root dùng để làm).
Bước 7: Password policy cho tài khoản.
aws iam update-account-password-policy \
--minimum-password-length 14 \
--require-symbols --require-numbers \
--require-uppercase-characters --require-lowercase-characters \
--allow-users-to-change-password \
--max-password-age 90 \
--password-reuse-prevention 5 \
--profile shop-lab
Console: IAM → Account settings → Password policy → Edit.
Bước 8: Budget. Budgets API là dịch vụ global, endpoint ở us-east-1, CLI tự lo.
export BUDGET_EMAIL=ban@example.com # email thật của bạn
cat > budget.json <<EOF
{
"BudgetName": "shop-lab-monthly",
"BudgetType": "COST",
"TimeUnit": "MONTHLY",
"BudgetLimit": { "Amount": "10", "Unit": "USD" }
}
EOF
cat > budget-notifications.json <<EOF
[
{
"Notification": {
"NotificationType": "ACTUAL",
"ComparisonOperator": "GREATER_THAN",
"Threshold": 80, "ThresholdType": "PERCENTAGE"
},
"Subscribers": [{ "SubscriptionType": "EMAIL", "Address": "${BUDGET_EMAIL}" }]
},
{
"Notification": {
"NotificationType": "FORECASTED",
"ComparisonOperator": "GREATER_THAN",
"Threshold": 100, "ThresholdType": "PERCENTAGE"
},
"Subscribers": [{ "SubscriptionType": "EMAIL", "Address": "${BUDGET_EMAIL}" }]
}
]
EOF
aws budgets create-budget \
--account-id ${ACCOUNT_ID} \
--budget file://budget.json \
--notifications-with-subscribers file://budget-notifications.json \
--profile shop-lab
aws budgets describe-budgets --account-id ${ACCOUNT_ID} --profile shop-lab \
--query 'Budgets[].{Name:BudgetName,Limit:BudgetLimit.Amount}'
Console: Billing and Cost Management → Budgets → Create budget → Customize → Cost budget. Bạn sẽ nhận email xác nhận subscription; bấm xác nhận.
Bước 9: Lưu biến cho các lab sau.
cat > ~/shop-lab.env <<EOF
export AWS_REGION=ap-southeast-1
export AWS_PROFILE=shop-lab
export ACCOUNT_ID=${ACCOUNT_ID}
EOF
# mỗi lần mở terminal mới: source ~/shop-lab.env
Các lab sau giả định bạn đã source ~/shop-lab.env, nhưng vẫn viết --profile shop-lab tường minh để không nhầm tài khoản.
Kiểm tra kết quả
# 1. Danh tính qua role
aws sts get-caller-identity --profile shop-lab --query Arn --output text
# arn:aws:sts::123456789012:assumed-role/ShopAdminRole/botocore-session-...
# 2. User không còn quyền admin trực tiếp: lệnh này phải AccessDenied
aws s3 ls --profile shop-admin
# An error occurred (AccessDenied) ... s3:ListAllMyBuckets ... because no identity-based policy allows
# 3. Qua role thì được (danh sách rỗng là bình thường, chưa có bucket)
aws s3 ls --profile shop-lab
# 4. Root không có key, user có đúng 1 key, MFA đã bật
aws iam get-account-summary --profile shop-lab \
--query 'SummaryMap.{RootKeys:AccountAccessKeysPresent,RootMFA:AccountMFAEnabled,Users:Users,Roles:Roles}'
# { "RootKeys": 0, "RootMFA": 1, "Users": 1, "Roles": ... }
aws iam list-mfa-devices --user-name shop-admin --profile shop-lab
# 5. Credential report: xem tổng quan toàn tài khoản (chờ vài giây rồi get)
aws iam generate-credential-report --profile shop-lab
aws iam get-credential-report --profile shop-lab --query Content --output text | base64 -d | column -s, -t | cut -c1-150
Thử thêm
- Bỏ điều kiện MFA rồi test. Sửa trust policy xóa khối
Condition,aws iam update-assume-role-policy --role-name ShopAdminRole --policy-document file://..., xóa dòngmfa_serialtrong profile → assume được mà không cần mã. Hiểu rồi thì khôi phục lại. Đây chính là khác biệt giữa "có role" và "role được bảo vệ". - Xem CloudTrail. Console: CloudTrail → Event history, lọc Event name = AssumeRole. Bạn thấy user shop-admin gọi AssumeRole, và các event sau đó mang danh
ShopAdminRole. Event history 90 ngày là miễn phí. - Switch role trên Console. Đăng nhập shop-admin → menu tài khoản → Switch role → Account: ACCOUNT_ID, Role: ShopAdminRole, Display name: shop-lab, chọn màu. Giờ Console cũng chạy bằng role.
- Giả lập lộ key.
aws iam update-access-key --user-name shop-admin --access-key-id AKIA... --status Inactive --profile shop-lab→ mọi lệnh với shop-admin lập tức thất bại. Đây là phản xạ đầu tiên khi nghi key bị lộ. Kích hoạt lại bằng--status Active.
Dọn tài nguyên
Lab này không tạo gì tốn tiền, và các lab sau cần user/role/profile này, nên giữ lại. Nếu bỏ hẳn tài khoản lab, xóa tài nguyên của các bài sau trước. Sau đó xóa Budget khi profile shop-lab còn hoạt động:
aws budgets delete-budget --account-id "$ACCOUNT_ID" --budget-name shop-lab-monthly --profile shop-lab
IAM dọn trong Console bằng root + MFA: vào IAM → Users → shop-admin, xóa access key, login profile, MFA và policy gắn với user, rồi xóa user; vào IAM → Policies xóa ShopAdminUserPolicy; vào IAM → Roles → ShopAdminRole, gỡ AdministratorAccess rồi xóa role. Cuối cùng xóa profile shop-admin/shop-lab khỏi ~/.aws/config và ~/.aws/credentials trên máy. Không tạo access key cho root để dọn lab.
Không dùng profile shop-admin để dọn sau khi xóa role: policy của user chỉ cho phép sts:AssumeRole và tự quản credential, không có quyền xóa IAM user hay policy. Nếu quyết định đóng tài khoản, root → Account → Close account sau khi xác nhận đã dọn các tài nguyên tính phí.
Phỏng vấn và đề thi SAA hỏi về tài khoản AWS và IAM cơ bản thì sao?
Q1. IAM user và IAM role khác nhau thế nào? Khi nào dùng cái nào?
🇻🇳 Tiếng Việt:
IAM user là danh tính có credential dài hạn: password và access key. IAM role là danh tính không có credential cố định; ai được trust policy cho phép sẽ gọi STS AssumeRole và nhận credential tạm, hết hạn tự vô hiệu. Em dùng role cho mọi workload trong AWS như EC2, ECS, Lambda, cho cross-account và cho con người nâng quyền tạm thời. User chỉ dùng khi không còn cách khác, luôn kèm MFA và tốt nhất chỉ để assume role.
🇬🇧 English:
An IAM user is an identity with long-term credentials: a password and access keys. An IAM role has no fixed credentials; whoever the trust policy allows calls STS AssumeRole and receives temporary credentials that expire automatically. I use roles for every workload inside AWS such as EC2, ECS and Lambda, for cross-account access, and for humans to elevate privileges temporarily. Users are only for cases with no alternative, always with MFA, and ideally only to assume roles.
Q2. Ứng dụng Spring Boot chạy trên EC2 cần đọc S3. Bạn cấp credential thế nào?
🇻🇳 Tiếng Việt:
Em không tạo access key. Em tạo một IAM role với permission chỉ đọc đúng bucket, gắn vào instance qua instance profile. SDK v2 dùng default credentials provider chain, tự lấy credential tạm từ instance metadata và tự refresh. Code chỉ là
S3Client.builder().build(), không có credential nào trong cấu hình. Ở local em dùng profile hoặc SSO, cùng một dòng code.
🇬🇧 English:
I would not create access keys. I would create an IAM role whose permissions only allow reading that bucket and attach it to the instance through an instance profile. The SDK v2 default credentials provider chain fetches temporary credentials from the instance metadata service and refreshes them automatically. The code is just
S3Client.builder().build()with no credentials in configuration. Locally I use a named profile or SSO with the exact same code.
Q3. Bạn bảo vệ root account và tài khoản AWS ngày đầu như thế nào?
🇻🇳 Tiếng Việt:
Bật MFA cho root, xóa mọi access key của root, cất password và chỉ dùng root cho việc bắt buộc. Tạo IAM user cá nhân có MFA, quyền hẹp, dùng để assume vào admin role có điều kiện MFA. Đặt password policy, bật CloudTrail, tạo Budget báo email khi vượt ngưỡng, bật Free Tier alert, và tag mọi tài nguyên để kiểm soát chi phí.
🇬🇧 English:
Enable MFA on root, delete any root access keys, lock the password away and use root only for the few tasks that require it. Create a personal IAM user with MFA and minimal permissions that assumes an admin role protected by an MFA condition. Set a password policy, enable CloudTrail, create a Budget that emails when spend crosses a threshold, turn on Free Tier alerts, and tag every resource for cost control.
Q4. Policy JSON có những phần nào? Implicit deny là gì?
🇻🇳 Tiếng Việt:
Một statement gồm Effect (Allow/Deny), Action, Resource dưới dạng ARN, Principal với resource-based policy, và Condition tùy chọn. Mặc định mọi request bị từ chối ngầm, gọi là implicit deny; chỉ khi có Allow khớp mới được, và một Deny tường minh luôn thắng mọi Allow.
🇬🇧 English:
A statement has an Effect (Allow or Deny), Actions, Resources as ARNs, a Principal in resource-based policies, and optional Conditions. By default every request is implicitly denied; it is only permitted when a matching Allow exists, and an explicit Deny always overrides any Allow.
Góc thi SAA-C03
Câu 1: Một công ty cần cho developer truy cập tài nguyên AWS từ laptop bằng CLI. Yêu cầu: credential phải tạm thời, bắt buộc MFA, tuân thủ least privilege. Giải pháp nào ĐÁP ỨNG các yêu cầu này?
A. Tạo IAM user cho mỗi developer với AdministratorAccess, bật MFA cho Console.
B. Tạo IAM user với quyền tối thiểu chỉ gồm sts:AssumeRole; tạo IAM role với quyền cần thiết và trust policy yêu cầu aws:MultiFactorAuthPresent; developer assume role từ CLI.
C. Dùng chung một access key của root cho cả team, lưu trong Secrets Manager.
D. Tạo IAM group gắn PowerUserAccess, thêm developer vào group và phát access key.
Đáp án: B. Role cho credential tạm, điều kiện MFA trong trust policy bắt buộc MFA kể cả khi dùng CLI, quyền thật nằm ở role nên user chỉ cần tối thiểu. A: user có quyền admin dài hạn, MFA chỉ bảo vệ Console, access key không có MFA. C: dùng root và chia sẻ credential, vi phạm mọi nguyên tắc. D: access key dài hạn, không có MFA cho CLI, PowerUser không phải least privilege.
Câu 2: Một ứng dụng chạy trên EC2 cần ghi object vào một S3 bucket. Solutions architect phải chọn cách cấp quyền AN TOÀN NHẤT với ÍT công việc vận hành nhất.
A. Tạo IAM user, sinh access key, lưu trong file cấu hình trên instance và xoay key thủ công mỗi 90 ngày.
B. Tạo IAM role có quyền s3:PutObject lên bucket, gắn role vào instance qua instance profile.
C. Lưu access key của root trong AWS Secrets Manager và cho ứng dụng đọc ra lúc khởi động.
D. Đặt bucket policy cho phép principal * ghi vào bucket để ứng dụng không cần credential.
Đáp án: B. Instance profile cấp credential tạm qua IMDS, SDK tự refresh, không có key để lộ hay phải xoay. A: credential dài hạn trên đĩa, phải tự xoay. C: root key không bao giờ được dùng, Secrets Manager không sửa được điều đó. D: bucket public cho ghi, lỗ hổng nghiêm trọng.
Câu 3: Một startup muốn được thông báo qua email khi chi phí AWS DỰ BÁO của tháng vượt 500 USD, và khi chi phí thực tế đạt 80% ngưỡng đó. Giải pháp nào có ÍT công việc nhất?
A. Tạo CloudWatch alarm trên metric EstimatedCharges ở us-east-1 với ngưỡng 400 USD.
B. Dùng Cost Explorer, lập lịch export báo cáo hằng ngày sang S3 và viết Lambda phân tích.
C. Tạo AWS Budget loại Cost với hai notification: ACTUAL > 80% và FORECASTED > 100%, subscriber là email.
D. Bật AWS Cost Anomaly Detection với ngưỡng 500 USD.
Đáp án: C. Budgets hỗ trợ cả ngưỡng thực tế và dự báo trong một budget, gửi email trực tiếp. A: billing alarm chỉ theo thực tế, không có dự báo, và phải ở us-east-1. B: Cost Explorer để phân tích, không phải cảnh báo; tự viết Lambda là thừa việc. D: Anomaly Detection phát hiện bất thường theo mô hình, không phải ngưỡng ngân sách cố định.
Cách nhớ ngắn gọn
- Root: MFA + không key + cất đi. User: chìa khóa để mở role. Role: quyền thật, credential tạm.
- Role có hai policy: trust (ai vào) và permission (vào rồi làm gì).
AKIA= key dài hạn,ASIA= key tạm từ STS.- Lỗi quyền: gõ
get-caller-identitytrước, đọc hết thông điệp AccessDenied. - Ngày đầu: Budget + Free Tier alert + tag
Project/Env.
| Từ khóa trong đề | Nghĩ đến |
|---|---|
| application on EC2/Lambda/ECS needs access | IAM role (instance profile / execution role / task role) |
| temporary credentials, cross-account | STS AssumeRole |
| enforce MFA for API/CLI | Condition aws:MultiFactorAuthPresent |
| centralized login, many accounts, Active Directory | IAM Identity Center |
| alert when forecasted cost exceeds | AWS Budgets |
| analyze spend by service/tag | Cost Explorer |
Tự kiểm tra
Vì sao không bao giờ tạo access key cho root user?
Root không thể bị giới hạn bằng policy. Key lộ là mất toàn quyền tài khoản, không có lớp nào chặn. Việc cần root chỉ làm qua Console có MFA.
Một role cần hai policy nào để dùng được?
Trust policy (assume role policy document) quy định principal nào được sts:AssumeRole; permission policy quy định sau khi assume được gọi action gì. Thiếu một trong hai là không dùng được.
Profile shop-lab trong ~/.aws/config có role_arn và source_profile nghĩa là gì?
CLI dùng credential của source_profile (shop-admin) để gọi STS AssumeRole lên role_arn, kèm MFA nếu có mfa_serial, rồi dùng credential tạm nhận được cho lệnh; token cache ở ~/.aws/cli/cache.
InvalidClientTokenId và AccessDenied khác nhau thế nào?
InvalidClientTokenId: AWS không nhận ra access key (sai, bị xóa, nhầm tài khoản), chưa tới bước kiểm tra quyền. AccessDenied: credential hợp lệ nhưng không policy nào cho phép, hoặc có Deny.
Thứ tự default credentials provider chain của SDK v2?
System property → biến môi trường → web identity token (IRSA) → profile ~/.aws → ECS container credentials → EC2 instance metadata. Cùng một code chạy ở local và trên AWS.
🧪 Thực hành bài này: Basic: Nền tảng: IAM + VPC + EC2 (chưa triển khai) · lab labs/aws