Tài khoản AWS & IAM cơ bản: root, user, role, policy, MFA, CLI, Budget

6 câu hỏi~45 phútTừ khóa: root user, IAM user, group, role, policy, MFA, access key, STS, CLI profile, AssumeRole, AWS Budgets, Free Tier, tag · SAA: Domain 3 Secure

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:

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ả:

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ệmLà gìDùng cho
Root userChủ tài khoản, toàn quyền, không giới hạn đượcChỉ 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 userDanh 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 groupTập hợp user để gắn policy chungGom quyền theo vai (developers, admins). Group không lồng nhau
IAM roleDanh 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
PolicyTài liệu JSON: Effect / Action / Resource / ConditionGắn vào user, group, role (identity-based) hoặc vào resource như S3 bucket (resource-based)
STSSecurity 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
MFAYếu tố thứ hai: app TOTP, FIDO2 key, passkeyBắ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" }
      }
    }
  ]
}

Policy tồn tại ở ba dạng, đề thi và thực tế đều phân biệt:

DạngAi tạo / ai sửaGắn được vàoDùng khi
AWS managed (arn:aws:iam::aws:policy/...)AWS tạo và cập nhật khi có API mớiNhiều user/group/role, nhiều accountBắ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 accountQuyền chuẩn của team: ShopAdminUserPolicy, ShopAppS3ReadPolicy. Nên là mặc định
InlineNhúng thẳng vào một user/group/role, không có ARN riêngĐúng một principalQuan 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 keyIAM role (assume)IAM Identity Center (SSO)
CredentialDài hạn, không hết hạnTạm (15 phút – 12 giờ)Tạm, lấy qua trình duyệt (aws sso login)
Rủi ro lộ keyCao: lộ là dùng được tới khi bị xóaThấp: hết hạn tự vô hiệuThấp
Dùng choMá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ềnCon người ở tổ chức nhiều account
RotationPhải tự xoay (khuyến nghị ≤ 90 ngày)Tự độngTự độ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

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ể

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:

  1. get-caller-identity: đúng danh tính chưa?
  2. Action đúng tên chưa? s3 ls không bucket = s3:ListAllMyBuckets; s3 ls s3://bucket = s3:ListBucket; tải file = s3:GetObject.
  3. Resource ARN đúng chưa? Bucket vs bucket/*.
  4. Có Deny ở đâu đó không (SCP, permission boundary, bucket policy, điều kiện MFA)? Bài 22.2.
  5. 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ểuVí 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 GB750 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ắnMột số dịch vụ 30–90 ngày đầuKiể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 ExplorerBiểu đồ, lọc theo service/tag/region, dự báoPhân tích: "tiền đi đâu tháng này"
Cost Anomaly DetectionMachine learning phát hiện chi tiêu bất thườngPhát hiện đột biến, miễn phí
CloudWatch billing alarmAlarm trên metric EstimatedCharges (chỉ ở us-east-1)Cách cũ, Budgets thay được
Free Tier alertsEmail khi dùng tới 85% hạn mức Free TierBậ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

Chi phí & thời gian

Chuẩn bị

Các bước

Bước 1: Khóa root. Đăng nhập Console bằng root.

Bước 2: Tạo user bootstrap shop-admin (Console, vì chưa có CLI).

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

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

Từ khóa trong đềNghĩ đến
application on EC2/Lambda/ECS needs accessIAM role (instance profile / execution role / task role)
temporary credentials, cross-accountSTS AssumeRole
enforce MFA for API/CLICondition aws:MultiFactorAuthPresent
centralized login, many accounts, Active DirectoryIAM Identity Center
alert when forecasted cost exceedsAWS Budgets
analyze spend by service/tagCost 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