IAM sâu: policy evaluation, STS AssumeRole, resource policy, permission boundary, Organizations & SCP
Tiếp đi: IAM sâu hơn. Role ShopAdminRole của mình có AdministratorAccess (tức Allow *:*) mà khi tải ảnh từ bucket shop-app-assets vẫn bị AccessDenied. Hóa ra bucket policy có một dòng Deny. Vậy khi vừa có Allow vừa có Deny thì AWS quyết định theo luật nào? Mình muốn hiểu hẳn cách IAM "nghĩ".
Câu hỏi này đúng trọng tâm. Người mới xem IAM như một danh sách "được / không được", nhưng thật ra nó là một thuật toán quyết định gom nhiều loại policy từ nhiều nơi. Hiểu thuật toán đó thì mọi lỗi AccessDenied đều giải thích được, và khoảng 1/4 câu hỏi Domain 3 của SAA xoay quanh nó.
1. Bài toán: shop-app có nhiều "cửa" kiểm tra quyền cùng lúc
Tới bài này shop-app trên AWS đã có vài loại policy đụng nhau:
Ai gọi Policy liên quan tới một request GetObject
───────────────────────── ─────────────────────────────────────────────────────
ShopAdminRole (bạn, CLI) permission policy của role : Allow *:*
bucket policy shop-app-assets : Deny GetObject nếu không qua VPC endpoint
(tương lai) SCP của Organizations: Deny mọi region ngoài ap-southeast-1
(tương lai) permission boundary : giới hạn user shop-dev
shop-app trên EC2 instance role shop-app-ec2-role : Allow s3:GetObject bucket/*
Team analytics (account B) cross-account: role ở account A + bucket policy
Câu hỏi "được hay không" không trả lời được nếu chỉ nhìn một policy.
2. Vấn đề: ba hiểu nhầm phổ biến
- "Admin thì làm gì cũng được." Sai:
Allow *vẫn thua mộtDenytường minh, ở bất kỳ policy nào (bucket policy, SCP, boundary). Chỉ root mới không bị identity-based và boundary chặn, nhưng vẫn bị SCP chặn. - "Có bucket policy cho phép là đủ." Đúng trong cùng account; sai khi cross-account: bên gọi cũng phải có identity policy cho phép.
- "Permission boundary là để cấp quyền." Sai: boundary chỉ giới hạn quyền tối đa, không cấp gì cả. SCP cũng vậy.
3. Giải pháp: thuật toán policy evaluation
Với một request trong cùng một account, IAM chạy đúng thứ tự này:
Request đến (principal, action, resource, context keys)
│
▼
① Có Deny tường minh ở BẤT KỲ policy nào? ──── có ──► DENY (explicit deny, dừng)
│ không
▼
② Organizations SCP: có Allow cho action này? ── không ──► DENY (implicit)
│ có (tài khoản không trong Organization: bỏ qua bước này)
▼
③ Resource-based policy: có Allow cho principal này? ── có ──► ALLOW (trong cùng account,
│ không đủ một bên Allow là được)
▼
④ Identity-based policy (user/group/role): có Allow? ── không ──► DENY (implicit)
│ có
▼
⑤ Permission boundary (nếu principal có): có Allow? ── không ──► DENY (implicit)
│ có
▼
⑥ Session policy (nếu AssumeRole truyền thêm): có Allow? ── không ──► DENY (implicit)
│ có
▼
ALLOW
Nói gọn bằng ba câu, đúng thứ tự ưu tiên:
- Explicit Deny thắng tất cả. Lỗi của bạn ở bucket policy thuộc loại này.
- Mặc định là implicit deny; phải có Allow mới được.
- Allow phải lọt qua mọi lớp giới hạn: SCP ∩ boundary ∩ session policy. Mỗi lớp là một "bộ lọc" thu hẹp, không cấp quyền.
Hình ảnh hay dùng: quyền hiệu lực = giao (intersection) của các vòng:
┌──────────── SCP (Organization cho phép) ─────────────┐
│ ┌──────── Permission boundary (tối đa của user) ──┐ │
│ │ ┌──── Identity policy (user thực sự được) ─┐ │ │
│ │ │ ███ quyền hiệu lực ███ │ │ │
│ │ └──────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────┘ │
└───────────────────────────────────────────────────────┘
Deny tường minh ở bất kỳ vòng nào: đục một lỗ xuyên qua tất cả
Các thuật ngữ đề thi gắn với từng lớp:
| Lớp | Gắn vào | Cấp quyền? | Ai quản | Từ khóa đề |
|---|---|---|---|---|
| Identity-based policy | User, group, role | Có | Admin account | "grant permissions to the role" |
| Resource-based policy | S3 bucket, SQS queue, KMS key, Lambda, Secrets Manager, ECR repo… | Có (kể cả cho principal account khác) | Chủ resource | "cross-account access to S3 bucket", "allow another account to send to the queue" |
| Permission boundary | User, role (một managed policy) | Không, chỉ giới hạn | Admin account | "allow developers to create roles but not exceed their own permissions", "delegate IAM safely" |
| SCP (Service Control Policy) | Root, OU, account trong Organizations | Không, chỉ giới hạn; ảnh hưởng cả root user của account con | Management account | "prevent all accounts from…", "restrict regions", "guardrail" |
| Session policy | Truyền khi AssumeRole / GetFederationToken | Không, chỉ giới hạn phiên | Người gọi | "further restrict temporary credentials" |
| ACL (S3, legacy) | Bucket/object | Có, nhưng khuyến nghị tắt | Chủ object | Thường là đáp án sai |
4. Cấu hình từng bước: các mảnh ghép
Bước 1: Condition và context key. Phần mạnh nhất của policy. Context key là thông tin AWS biết về request lúc đánh giá:
{
"Sid": "DenyOutsideSingaporeExceptGlobalServices",
"Effect": "Deny",
"NotAction": ["iam:*", "sts:*", "organizations:*", "budgets:*", "support:*",
"cloudfront:*", "route53:*", "waf:*", "health:*"],
"Resource": "*",
"Condition": {
"StringNotEquals": { "aws:RequestedRegion": "ap-southeast-1" }
}
}
Đọc: từ chối mọi action không thuộc danh sách dịch vụ global, nếu region yêu cầu khác Singapore. Các context key hay dùng:
| Key | Ý nghĩa | Ví dụ dùng |
|---|---|---|
aws:RequestedRegion | Region của API call | Khóa region (SCP) |
aws:SourceIp | IP public của người gọi | Chỉ cho từ VPN công ty. Lưu ý: gọi từ EC2 qua VPC endpoint thì không phải IP public |
aws:SourceVpce / aws:SourceVpc | VPC endpoint / VPC nguồn | Bucket policy: chỉ nhận request qua endpoint của shop-vpc (chính là Deny làm bạn vấp) |
aws:MultiFactorAuthPresent | Phiên có MFA | Trust policy ShopAdminRole (bài 22.1) |
aws:PrincipalTag/Team, aws:ResourceTag/Env | Tag của người gọi / của resource | ABAC: user tag Team=shop chỉ sửa resource tag Team=shop |
aws:PrincipalOrgID | Organization ID của người gọi | Bucket policy: cho mọi account trong tổ chức, không cần liệt kê |
aws:SecureTransport | Request qua TLS? | Deny nếu false (bắt buộc HTTPS vào S3) |
sts:ExternalId | Chuỗi bí mật bên thứ ba truyền khi AssumeRole | Chống confused deputy |
iam:PermissionsBoundary | Boundary gắn khi tạo user/role | Bắt dev phải gắn boundary khi tạo role |
Operator: StringEquals, StringLike (wildcard), ArnLike, IpAddress, Bool, Null (key có tồn tại không), DateGreaterThan; hậu tố IfExists (bỏ qua nếu key không có), tiền tố ForAllValues:/ForAnyValue: cho key nhiều giá trị.
Bước 2: STS AssumeRole, lời gọi đứng sau mọi role.
aws sts assume-role \
--role-arn arn:aws:iam::123456789012:role/ShopReadOnlyRole \
--role-session-name tuan-debug-2025-01-10 \
--duration-seconds 3600 \
--profile shop-dev
{
"Credentials": {
"AccessKeyId": "ASIA...", ← tạm
"SecretAccessKey": "...",
"SessionToken": "IQoJb3JpZ2luX2Vj...", ← phải gửi kèm mọi request
"Expiration": "2025-01-10T09:00:00+00:00"
},
"AssumedRoleUser": {
"Arn": "arn:aws:sts::123456789012:assumed-role/ShopReadOnlyRole/tuan-debug-2025-01-10"
}
}
Ba biến thể cần phân biệt (đề thi hỏi):
| API | Ai gọi | Khi nào |
|---|---|---|
AssumeRole | Principal IAM (user/role) đã có credential AWS | Cross-account, nâng quyền, EC2/ECS/Lambda bên dưới cũng dùng cái này |
AssumeRoleWithWebIdentity | Mang JWT từ OIDC provider (Cognito, Google, GitHub Actions, EKS IRSA) | Mobile app, CI không có key AWS, pod trong EKS |
AssumeRoleWithSAML | Mang SAML assertion từ IdP doanh nghiệp (AD FS, Okta) | Federation SSO kiểu cũ; nay thường qua IAM Identity Center |
GetSessionToken | IAM user, kèm MFA | Lấy credential tạm có MFA cho chính user (không đổi vai) |
GetFederationToken | Proxy có key dài hạn cấp token cho user ngoài | Ít gặp, dùng cho identity broker tự viết |
Role chaining: dùng credential của role A để assume role B. Được, nhưng phiên bị giới hạn tối đa 1 giờ bất kể max-session-duration. Đề hay gài: "session expires after 1 hour even though the role allows 12 hours" → role chaining.
Bước 3: Cross-account và ExternalId. Team analytics ở account B (222222222222) muốn đọc bucket shop-app-assets ở account A (111111111111). Cách chuẩn: tạo role ở A, trust B.
// Account A: trust policy của role ShopAssetsReaderRole
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::222222222222:root" }, // "root" = mọi principal của B
"Action": "sts:AssumeRole", // mà B cho phép assume
"Condition": { "StringEquals": { "sts:ExternalId": "shop-analytics-7f3a9c" } }
}]
}
// Account A: permission policy của role: s3:GetObject/ListBucket trên shop-app-assets
// Account B: identity policy cho analytics role/user
{ "Effect": "Allow", "Action": "sts:AssumeRole",
"Resource": "arn:aws:iam::111111111111:role/ShopAssetsReaderRole" }
Vì sao cần ExternalId: giả sử bạn thuê một SaaS giám sát (account V) và cho họ assume role. Khách hàng khác của V cũng cho V assume role của họ. Nếu kẻ xấu biết ARN role của bạn, hắn khai ARN đó vào tài khoản của hắn ở V; V (được bạn trust) sẽ assume role của bạn thay cho hắn. Đó là confused deputy. ExternalId là chuỗi V sinh riêng cho từng khách; V luôn truyền đúng ExternalId của khách đang yêu cầu, nên request của kẻ xấu mang ExternalId của hắn, không khớp với role của bạn. ExternalId không phải mật khẩu; nó là định danh phân biệt khách hàng.
Bước 4: Cross-account không cần assume role: resource-based policy. Với dịch vụ có resource policy (S3, SQS, SNS, KMS, Lambda, Secrets Manager…), account A có thể viết thẳng principal của B vào policy của resource. Khi đó cả hai bên phải Allow: bucket policy ở A cho B, và identity policy ở B cho principal gọi.
Cross-account, dịch vụ CÓ resource policy (S3):
B identity policy Allow ∧ A bucket policy Allow (principal B) → OK, không đổi vai
Ưu: giữ nguyên danh tính B (CloudTrail ở A thấy principal của B), gọi nhiều account cùng lúc
Cross-account, dịch vụ KHÔNG có resource policy (EC2, RDS, DynamoDB*):
bắt buộc assume role ở A
(*DynamoDB có resource-based policy từ 2024, nhưng đề thi cũ vẫn coi là không)
Chú ý S3 object ownership: object do B ghi vào bucket của A, với BucketOwnerEnforced (mặc định mới) thì A sở hữu; với cách cũ ACL thì B sở hữu và A có thể không đọc được chính object trong bucket mình. Đề thi dạng "bucket owner cannot access objects uploaded by another account" → bật Object Ownership = Bucket owner enforced (bài 22.6).
Bước 5: Permission boundary, ủy quyền IAM cho dev một cách an toàn. Bạn muốn dev tự tạo role cho Lambda của họ, nhưng không được tạo role mạnh hơn chính họ. Cách làm:
// 1. Boundary: quyền TỐI ĐA của dev và của mọi role dev tạo ra
ShopDeveloperBoundary = Allow { s3:*, logs:*, cloudwatch:*, lambda:*, sqs:*, dynamodb:* }
trong ap-southeast-1, không có iam:* trừ PassRole/Get/List
// 2. Identity policy của dev: được iam:CreateRole / AttachRolePolicy
// CHỈ KHI role mới cũng gắn boundary này
{
"Effect": "Allow",
"Action": ["iam:CreateRole", "iam:AttachRolePolicy", "iam:PutRolePolicy"],
"Resource": "arn:aws:iam::111111111111:role/shop-dev-*",
"Condition": {
"StringEquals": { "iam:PermissionsBoundary": "arn:aws:iam::111111111111:policy/ShopDeveloperBoundary" }
}
},
// 3. Cấm dev sửa/xóa chính boundary
{ "Effect": "Deny",
"Action": ["iam:DeleteRolePermissionsBoundary", "iam:DeleteUserPermissionsBoundary",
"iam:CreatePolicyVersion", "iam:DeletePolicy"],
"Resource": "arn:aws:iam::111111111111:policy/ShopDeveloperBoundary" }
Kết quả: dev có thể tạo shop-dev-image-resize-role gắn AmazonS3FullAccess, nhưng quyền hiệu lực của role đó = S3FullAccess ∩ Boundary, tức vẫn không thể đụng IAM hay region khác. Đây là cách privilege escalation qua tạo role bị chặn.
Bước 6: Organizations và SCP. Khi có từ hai account trở lên (dev, prod, lab, security), tạo AWS Organizations:
Root (Organization o-abc123)
├── Management account (chỉ dùng cho billing + Organizations; không chạy workload)
├── OU: Security
│ └── account: audit (CloudTrail org trail, GuardDuty delegated admin)
├── OU: Workloads
│ ├── OU: Prod → account: shop-prod
│ └── OU: NonProd → account: shop-dev, shop-lab
└── OU: Sandbox → account cá nhân của từng dev
SCP gắn ở OU, kế thừa xuống account con:
Root : FullAWSAccess (mặc định) + DenyLeaveOrganization + DenyRootUserActions
Workloads : DenyRegionsExceptSingapore
NonProd : DenyExpensiveInstances (StringNotLike ec2:InstanceType = t3.*, t4g.*)
Sandbox : trên + DenyCreateIAMUser
Hai kiểu viết SCP: deny list (giữ FullAWSAccess, thêm các Deny; phổ biến, dễ bảo trì) và allow list (gỡ FullAWSAccess, chỉ Allow dịch vụ được dùng; chặt nhưng dễ vỡ khi thêm dịch vụ). Điểm thi quan trọng: SCP không ảnh hưởng management account, nhưng có ảnh hưởng root user của account con; SCP không cấp quyền, chỉ đặt trần. Lợi ích đi kèm Organizations: consolidated billing (một hóa đơn, gộp volume discount và Reserved Instance/Savings Plans sharing), tạo account bằng API, tích hợp Identity Center, Control Tower dựng "landing zone" sẵn.
Bước 7: Java: assume role trong code khi thật sự cần. Phần lớn thời gian bạn không gọi STS trong code (chain tự lo). Trường hợp cần: shop-app ở account A phải ghi vào SQS của account B qua role.
// software.amazon.awssdk:sts, software.amazon.awssdk:sqs
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.services.sts.StsClient;
import software.amazon.awssdk.services.sts.auth.StsAssumeRoleCredentialsProvider;
import software.amazon.awssdk.services.sts.model.AssumeRoleRequest;
import software.amazon.awssdk.services.sqs.SqsClient;
@Configuration
public class PartnerSqsConfig {
@Bean
AwsCredentialsProvider partnerCredentials(
@Value("${shop.partner.role-arn}") String roleArn,
@Value("${shop.partner.external-id}") String externalId) { // từ Secrets Manager, không hardcode
StsClient sts = StsClient.create(); // credential của chính shop-app (role EC2)
return StsAssumeRoleCredentialsProvider.builder()
.stsClient(sts)
.refreshRequest(AssumeRoleRequest.builder()
.roleArn(roleArn)
.roleSessionName("shop-app-" + System.getenv().getOrDefault("HOSTNAME", "local"))
.externalId(externalId)
.durationSeconds(3600)
.build())
.asyncCredentialUpdateEnabled(true) // tự refresh trước khi hết hạn
.build();
}
@Bean
SqsClient partnerSqs(AwsCredentialsProvider partnerCredentials) {
return SqsClient.builder()
.credentialsProvider(partnerCredentials)
.build();
}
}
StsAssumeRoleCredentialsProvider cache credential và gọi lại STS trước khi hết hạn, nên bạn không tự quản Expiration. Session name nên có ý nghĩa: CloudTrail ở account B sẽ hiển thị assumed-role/PartnerRole/shop-app-ip-10-0-1-23.
5. Cách hoạt động: lần theo lỗi của bạn
aws s3 cp s3://shop-app-assets-111111111111/p/1.jpg . --profile shop-lab (chạy từ laptop)
① Explicit Deny?
bucket policy:
{ "Effect": "Deny", "Principal": "*", "Action": "s3:GetObject",
"Resource": "arn:aws:s3:::shop-app-assets-111111111111/*",
"Condition": { "StringNotEquals": { "aws:SourceVpce": "vpce-0abc" } } }
request từ laptop → không có aws:SourceVpce → StringNotEquals khớp → DENY ✘
(Allow *:* của ShopAdminRole không bao giờ được xét tới)
Cách sửa tùy ý định: thêm ngoại lệ cho role admin vào điều kiện ("ArnNotLike": {"aws:PrincipalArn": "arn:aws:iam::111111111111:role/ShopAdminRole"}), hoặc chấp nhận rằng dữ liệu chỉ đọc được từ trong VPC và dùng Session Manager vào một EC2 để thao tác. Đây là một thiết kế cố ý phổ biến: data chỉ ra khỏi S3 qua endpoint nội bộ.
Và trường hợp cùng account, chỉ cần một bên Allow:
shop-app trên EC2 (role shop-app-ec2-role: Allow s3:GetObject bucket/*) qua VPC endpoint vpce-0abc
① Deny? điều kiện SourceVpce khớp vpce-0abc → không Deny
② SCP: không có Organization → bỏ qua
③ Resource policy Allow cho role này? bucket policy không có Allow → tiếp
④ Identity policy: Allow s3:GetObject ✔
⑤ Boundary: role không có → bỏ qua
→ ALLOW
6. So sánh: những cặp đề thi hay đặt cạnh nhau
| Cặp | Khác nhau cốt lõi | Chọn khi |
|---|---|---|
| Identity-based vs Resource-based | Gắn vào ai gọi vs gắn vào thứ bị gọi; resource-based có Principal | Resource-based cho cross-account không đổi vai, cho dịch vụ AWS gọi vào (SNS → SQS, CloudWatch Logs → Kinesis) |
| Permission boundary vs SCP | Boundary: một principal, trong account, admin account quản. SCP: cả account/OU, management account quản, ảnh hưởng root | Boundary: ủy quyền tạo IAM cho dev. SCP: guardrail toàn tổ chức (region, dịch vụ cấm) |
| Trust policy vs Permission policy (của role) | Ai được assume vs assume rồi làm gì | Luôn cần cả hai |
| AssumeRole vs AssumeRoleWithWebIdentity | Đã có credential AWS vs mang JWT từ OIDC provider | Web identity cho mobile (Cognito), GitHub Actions, EKS pod |
| RBAC vs ABAC | Role theo chức danh, nhiều policy vs vài policy dùng tag điều kiện | ABAC khi team/project mọc nhanh, tag chuẩn hóa tốt |
| IAM Access Analyzer vs Policy Simulator | Tìm resource chia sẻ ra ngoài / sinh policy từ CloudTrail / validate cú pháp vs chạy thử "principal X gọi Y được không" | Analyzer để audit định kỳ; Simulator để debug một lỗi cụ thể |
| Deny trong SCP vs Deny trong NACL | Khác tầng hoàn toàn: SCP là quyền gọi API, NACL là gói tin mạng | Đừng nhầm khi đề nói "block" chung chung |
7. Khi nào dùng / không dùng
- Resource-based policy: khi muốn một dịch vụ AWS khác hay account khác gọi vào resource mà không đổi vai; khi cần khóa chặt "chỉ qua VPC endpoint", "chỉ HTTPS".
- AssumeRole cross-account: khi dịch vụ không có resource policy, hoặc muốn một điểm kiểm soát duy nhất (role) để audit và thu hồi.
- Permission boundary: khi ủy quyền tạo IAM entity cho team; nếu không ai ngoài admin tạo role thì không cần.
- SCP: khi có Organizations và cần guardrail không ai trong account con vượt được (kể cả admin của account). Không dùng SCP để cấp quyền hay để quản lý quyền chi tiết theo user.
- Không: viết
AllowvớiResource: "*"rồi "chặn lại bằng Deny"; cách này mong manh và khó audit. Thu hẹp Allow trước, Deny chỉ cho guardrail.
Mình thấy hơi rối ở điểm "cùng account thì một bên Allow là đủ". Vậy nếu team analytics ở account khác đọc bucket của mình, mình chỉ cần viết bucket policy cho account họ là xong, hay họ còn phải làm gì? Và khi nào thì nên dùng role thay vì bucket policy?
Bạn hiểu đúng nửa đầu. Luật tóm gọn:
Cùng account : identity Allow HOẶC resource Allow → được (trừ khi có Deny)
Khác account : identity Allow (bên gọi) VÀ resource Allow (bên có resource) → được
1. Vì sao khác account cần cả hai
Mỗi account là một ranh giới tin cậy. Bucket policy ở account A nói "tôi cho account B vào"; nhưng admin của B cũng phải nói "principal này của tôi được đi ra gọi bucket đó". Thiếu một trong hai, request bị implicit deny. Nên trả lời đồng đội analytics: "Bucket policy mình đã cho arn:aws:iam::222222222222:root; bên bạn gắn thêm identity policy s3:GetObject trên ARN bucket của mình cho role đang chạy job."
// Account A: bucket policy (cho cả account B, B tự chia nhỏ bằng identity policy)
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "AnalyticsRead",
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::222222222222:root" },
"Action": ["s3:GetObject", "s3:ListBucket"],
"Resource": ["arn:aws:s3:::shop-app-assets-111111111111",
"arn:aws:s3:::shop-app-assets-111111111111/*"]
}]
}
Dùng :root của account B có nghĩa "ủy quyền cho admin của B quyết định ai"; nếu muốn chặt hơn, ghi đúng ARN role của B. Nhưng cẩn thận: nếu B xóa và tạo lại role cùng tên, AWS đã thay ARN trong policy bằng unique ID nội bộ của role cũ, policy sẽ hiện ARN lạ dạng AROA... và ngừng hoạt động. Đó là lý do thực tế hay dùng :root + điều kiện aws:PrincipalArn.
2. Bucket policy hay role?
| Resource policy (bucket policy) | Role ở A, B assume | |
|---|---|---|
| Danh tính khi gọi | Giữ danh tính của B | Trở thành role của A |
| Gọi nhiều account | Dễ: một credential đọc bucket của nhiều account | Phải đổi credential từng account |
| Dịch vụ không có resource policy (EC2, RDS, hầu hết) | Không dùng được | Cách duy nhất |
| Thu hồi | Sửa policy từng resource | Sửa một trust policy |
| Object ownership (S3 ghi vào) | Cần Bucket owner enforced để A sở hữu object B ghi | Object do role của A ghi → A sở hữu, tự nhiên |
| Đề thi | "read-only access to a bucket from another account" → cả hai đều hợp lệ; chọn theo gợi ý "without assuming a role" hoặc "retain identity" | "access resources in another account" chung chung, hoặc "write to bucket" (object ownership) → role |
3. Một bẫy nữa: KMS
Nếu bucket mã hóa bằng KMS key (SSE-KMS), bên B còn cần quyền kms:Decrypt trên key đó, và key policy của A phải cho B. Ba nơi phải đồng ý: bucket policy, key policy, identity policy của B. Lỗi "AccessDenied dù bucket policy đúng" với bucket SSE-KMS gần như luôn là key policy (bài 22.14).
Permission boundary và SCP đều không cấp quyền mà chỉ giới hạn. Vậy chúng khác nhau chỗ nào? Công ty mình có nhiều account, muốn chặn mọi người không tạo tài nguyên ngoài ap-southeast-1, kể cả admin của từng account; dùng cái nào?
Đúng, cả hai là "trần" (ceiling), và tình huống bạn nêu là SCP, vì chỉ SCP mới trói được admin (và root) của account con. Boundary là thứ admin của account tự gắn và cũng tự gỡ được.
1. So sánh thẳng
| Permission boundary | SCP | |
|---|---|---|
| Phạm vi | Một user/role cụ thể | Mọi principal trong account/OU, kể cả root |
| Ai đặt | Admin trong account (ai có iam:PutRolePermissionsBoundary) | Management account của Organizations |
| Ai bị | Chỉ principal được gắn; admin gỡ được | Cả admin của account con; management account không bị |
| Mục đích | Ủy quyền tạo IAM an toàn (dev tự tạo role cho Lambda) | Guardrail tổ chức: region, dịch vụ, chống xóa CloudTrail, chống rời Organization |
| Hình thức | Một managed policy bình thường | Tài liệu JSON riêng, không có Principal, không hỗ trợ mọi Condition |
| Yêu cầu | Không cần Organizations | Cần Organizations, bật "all features" |
2. SCP khóa region cho tổ chức
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "DenyAllOutsideSingapore",
"Effect": "Deny",
"NotAction": [
"a4b:*", "access-analyzer:*", "account:*", "aws-portal:*", "budgets:*", "ce:*",
"cloudfront:*", "cur:*", "globalaccelerator:*", "health:*", "iam:*",
"importexport:*", "kms:*", "organizations:*", "pricing:*", "route53:*",
"route53domains:*", "s3:GetAccountPublic*", "s3:ListAllMyBuckets", "shield:*",
"sts:*", "support:*", "trustedadvisor:*", "waf-regional:*", "waf:*", "wafv2:*"
],
"Resource": "*",
"Condition": {
"StringNotEquals": { "aws:RequestedRegion": ["ap-southeast-1"] },
"ArnNotLike": { "aws:PrincipalARN": "arn:aws:iam::*:role/OrgBreakGlassRole" }
}
}]
}
Hai chỗ người ta hay quên: (1) NotAction để chừa dịch vụ global, nếu không bạn khóa luôn IAM và STS và không ai đăng nhập được; (2) chừa một break-glass role. Gắn SCP vào OU Workloads, không gắn vào Root nếu Sandbox cần thử region khác.
3. Boundary cho tình huống khác
Boundary hợp khi: "developers must be able to create IAM roles for their applications but must not be able to escalate privileges". Lab bên dưới làm đúng việc này. SCP không giải quyết được vì SCP không nhìn "role mới tạo mạnh hơn người tạo hay không".
4. Ba lớp cùng lúc, đọc thế nào
dev shop-dev (boundary B) trong account shop-dev (SCP S) tạo role R gắn S3FullAccess:
quyền của dev = identity(dev) ∩ B ∩ S
quyền của role R = S3FullAccess ∩ B(bắt buộc gắn nhờ điều kiện iam:PermissionsBoundary) ∩ S
Lambda dùng R: không thể gọi IAM (B cấm), không thể sang us-east-1 (S cấm), dù S3FullAccess nói "*"
Trước khi gắn policy vào production mình muốn kiểm tra thử "principal này gọi action này có được không" mà không phải chạy thật rồi xem lỗi. AWS có công cụ nào cho việc đó không?
Có ba công cụ, mỗi cái trả lời một kiểu câu hỏi khác nhau. Dùng đúng cái thì tiết kiệm rất nhiều lần "sửa → deploy → AccessDenied → sửa".
1. IAM Policy Simulator: "X gọi Y trên Z được không?"
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::111111111111:role/shop-app-ec2-role \
--action-names s3:GetObject s3:PutObject s3:DeleteBucket \
--resource-arns arn:aws:s3:::shop-app-assets-111111111111/products/1.jpg \
--profile shop-lab \
--query 'EvaluationResults[].{action:EvalActionName,decision:EvalDecision,boundary:PermissionsBoundaryDecisionDetail.AllowedByPermissionsBoundary}' \
--output table
# s3:GetObject allowed
# s3:PutObject implicitDeny
# s3:DeleteBucket implicitDeny
Simulator tính identity policy, permission boundary, resource policy (nếu bạn truyền --resource-policy) và điều kiện bạn giả lập bằng --context-entries. Nó không tính SCP. Có cả bản Console: IAM → Policy simulator, chọn user/role, tick action, bấm Run. Biến thể simulate-custom-policy cho policy chưa gắn vào ai: dán JSON vào kiểm tra trước.
2. IAM Access Analyzer: lint và audit
- Validate policy: báo lỗi cú pháp, action không tồn tại, cảnh báo quá rộng. Chạy trong CI trước khi
terraform apply:aws accessanalyzer validate-policy \ --policy-document file://shop-app-s3-policy.json \ --policy-type IDENTITY_POLICY --profile shop-lab \ --query 'findings[].{type:findingType,issue:issueCode,detail:findingDetails}' - External access findings: quét bucket, role, KMS key, SQS… chia sẻ ra ngoài account/organization. Câu đề "identify resources shared with external entities" → đây.
- Unused access: role/key không dùng 90 ngày, quyền cấp mà chưa từng gọi.
- Policy generation: đọc CloudTrail 90 ngày của một role rồi sinh policy chỉ gồm action thực sự đã dùng. Cách thực dụng nhất để từ
AdministratorAccesslúc thử nghiệm xuống least privilege.
3. CloudTrail + IAM last accessed
Khi lỗi đã xảy ra: CloudTrail event có errorCode: AccessDenied và errorMessage giống thông điệp CLI. Tab Access Advisor / Last accessed trên mỗi role cho biết dịch vụ nào role chưa từng đụng, gợi ý cắt bớt.
Hỏi gì? Dùng gì?
"policy này gọi được A không?" (trước khi gắn) simulate-custom-policy / Policy Simulator
"role này gọi được A không?" (đã gắn) simulate-principal-policy
"policy này có lỗi/quá rộng không?" Access Analyzer validate-policy
"cái gì đang public/chia sẻ ra ngoài?" Access Analyzer external access
"role cần đúng những quyền gì?" Access Analyzer policy generation (từ CloudTrail)
"ai đã gọi gì, bị từ chối ở đâu?" CloudTrail
Cho mình bài lab để tự tay làm phần này nhé: assume role, policy simulator và permission boundary trên tài khoản shop-lab.
Lab 22.2: AssumeRole, Policy Simulator và Permission Boundary
Ta tạo thêm một "developer" shop-dev với quyền hẹp, cho dev này assume một role read-only, rồi bọc dev trong boundary và kiểm chứng rằng dev không thể tạo role mạnh hơn mình.
Mục tiêu
- User
shop-devassume đượcShopReadOnlyRolebằng cảaws sts assume-rolethủ công và profile cấu hình sẵn. simulate-principal-policycho kết quảallowed/implicitDenykhớp với lệnh chạy thật.shop-devtạo role mới chỉ thành công khi gắnShopDeveloperBoundary; role mới không gọi được IAM dù gắnAdministratorAccess.- Policy có lỗi bị
validate-policybắt trước khi gắn.
Chi phí & thời gian
- IAM, STS, Policy Simulator, Access Analyzer validate-policy: miễn phí. Không tạo tài nguyên tính tiền.
- Thời gian: 45–60 phút.
Chuẩn bị
- Hoàn thành Lab 22.1: profile
shop-labhoạt động,source ~/shop-lab.env(có$ACCOUNT_ID,$AWS_REGION). - Tạo thư mục làm việc:
mkdir -p ~/shop-lab/iam && cd ~/shop-lab/iam.
Các bước
Bước 1: Tạo boundary ShopDeveloperBoundary. Trần quyền của dev: vài dịch vụ, chỉ Singapore, IAM chỉ đọc + PassRole.
cat > boundary.json <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DevServicesInSingapore",
"Effect": "Allow",
"Action": ["s3:*", "logs:*", "cloudwatch:*", "lambda:*", "sqs:*", "dynamodb:*", "ec2:Describe*"],
"Resource": "*",
"Condition": { "StringEquals": { "aws:RequestedRegion": "ap-southeast-1" } }
},
{
"Sid": "IamReadAndPass",
"Effect": "Allow",
"Action": ["iam:Get*", "iam:List*", "iam:PassRole", "iam:SimulatePrincipalPolicy",
"sts:GetCallerIdentity", "sts:AssumeRole", "access-analyzer:ValidatePolicy"],
"Resource": "*"
}
]
}
EOF
aws iam create-policy --policy-name ShopDeveloperBoundary \
--policy-document file://boundary.json \
--tags Key=Project,Value=shop-app Key=Env,Value=lab --profile shop-lab
export BOUNDARY_ARN=arn:aws:iam::${ACCOUNT_ID}:policy/ShopDeveloperBoundary
Console: IAM → Policies → Create policy → JSON.
Bước 2: Tạo role ShopReadOnlyRole mà dev sẽ assume.
cat > trust-readonly.json <<EOF
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::${ACCOUNT_ID}:root" },
"Action": "sts:AssumeRole",
"Condition": {
"ArnEquals": { "aws:PrincipalArn": "arn:aws:iam::${ACCOUNT_ID}:user/shop-dev" }
}
}]
}
EOF
aws iam create-role --role-name ShopReadOnlyRole \
--assume-role-policy-document file://trust-readonly.json \
--max-session-duration 3600 \
--tags Key=Project,Value=shop-app Key=Env,Value=lab --profile shop-lab
aws iam attach-role-policy --role-name ShopReadOnlyRole \
--policy-arn arn:aws:iam::aws:policy/ReadOnlyAccess --profile shop-lab
Để ý cách viết trust: principal là :root của account + điều kiện aws:PrincipalArn. Cách này không bị "ARN hóa đá" khi user bị xóa tạo lại. Console: IAM → Roles → Create role → Custom trust policy.
Bước 3: Tạo user shop-dev có boundary và policy riêng.
aws iam create-user --user-name shop-dev \
--permissions-boundary ${BOUNDARY_ARN} \
--tags Key=Project,Value=shop-app Key=Env,Value=lab Key=Team,Value=shop --profile shop-lab
cat > shop-dev-policy.json <<EOF
{
"Version": "2012-10-17",
"Statement": [
{ "Sid": "AssumeReadOnly", "Effect": "Allow", "Action": "sts:AssumeRole",
"Resource": "arn:aws:iam::${ACCOUNT_ID}:role/ShopReadOnlyRole" },
{ "Sid": "WhoAmI", "Effect": "Allow",
"Action": ["sts:GetCallerIdentity", "iam:Get*", "iam:List*", "iam:SimulatePrincipalPolicy"],
"Resource": "*" },
{ "Sid": "CreateRolesOnlyWithBoundary", "Effect": "Allow",
"Action": ["iam:CreateRole", "iam:AttachRolePolicy", "iam:PutRolePolicy", "iam:TagRole"],
"Resource": "arn:aws:iam::${ACCOUNT_ID}:role/shop-dev-*",
"Condition": { "StringEquals": { "iam:PermissionsBoundary": "${BOUNDARY_ARN}" } } },
{ "Sid": "DeleteOwnRoles", "Effect": "Allow",
"Action": ["iam:DeleteRole", "iam:DetachRolePolicy", "iam:DeleteRolePolicy"],
"Resource": "arn:aws:iam::${ACCOUNT_ID}:role/shop-dev-*" },
{ "Sid": "S3ListOnly", "Effect": "Allow", "Action": "s3:ListAllMyBuckets", "Resource": "*" },
{ "Sid": "ProtectBoundary", "Effect": "Deny",
"Action": ["iam:DeleteRolePermissionsBoundary", "iam:DeleteUserPermissionsBoundary",
"iam:PutRolePermissionsBoundary", "iam:PutUserPermissionsBoundary",
"iam:CreatePolicyVersion", "iam:DeletePolicy", "iam:DeletePolicyVersion"],
"Resource": "${BOUNDARY_ARN}" }
]
}
EOF
aws iam put-user-policy --user-name shop-dev --policy-name ShopDevInline \
--policy-document file://shop-dev-policy.json --profile shop-lab
# access key cho dev (lab: chính bạn đóng vai dev)
aws iam create-access-key --user-name shop-dev --profile shop-lab \
--query 'AccessKey.{id:AccessKeyId,secret:SecretAccessKey}' --output text
# ghi hai giá trị vào:
aws configure --profile shop-dev # region ap-southeast-1, output json
Lưu ý Deny iam:PutRolePermissionsBoundary: nếu không, dev có thể đổi boundary của role sang một policy rộng hơn. Console: IAM → Users → Create user → Set permissions boundary, rồi Add permissions → Create inline policy.
Bước 4: Assume role thủ công, nhìn credential tạm.
aws sts get-caller-identity --profile shop-dev --query Arn --output text
# arn:aws:iam::111111111111:user/shop-dev
CREDS=$(aws sts assume-role \
--role-arn arn:aws:iam::${ACCOUNT_ID}:role/ShopReadOnlyRole \
--role-session-name shop-dev-lab \
--duration-seconds 900 \
--profile shop-dev --output json)
export AWS_ACCESS_KEY_ID=$(echo $CREDS | jq -r .Credentials.AccessKeyId)
export AWS_SECRET_ACCESS_KEY=$(echo $CREDS | jq -r .Credentials.SecretAccessKey)
export AWS_SESSION_TOKEN=$(echo $CREDS | jq -r .Credentials.SessionToken)
echo $AWS_ACCESS_KEY_ID # ASIA... (tạm)
echo $CREDS | jq -r .Credentials.Expiration
aws sts get-caller-identity --query Arn --output text # KHÔNG có --profile: dùng env
# arn:aws:sts::111111111111:assumed-role/ShopReadOnlyRole/shop-dev-lab
aws iam list-users --query 'Users[].UserName' # ReadOnly: được
aws iam create-user --user-name hacker # AccessDenied: ReadOnly không có quyền ghi
# Dọn env để khỏi lẫn các bước sau
unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN
Chờ 15 phút rồi chạy lại bằng ba biến đó: ExpiredToken. Đó chính là ưu điểm của credential tạm.
Bước 5: Assume role bằng profile (cách dùng hằng ngày).
cat >> ~/.aws/config <<EOF
[profile shop-dev-ro]
role_arn = arn:aws:iam::${ACCOUNT_ID}:role/ShopReadOnlyRole
source_profile = shop-dev
role_session_name = shop-dev-ro
region = ap-southeast-1
EOF
aws sts get-caller-identity --profile shop-dev-ro --query Arn --output text
# arn:aws:sts::...:assumed-role/ShopReadOnlyRole/shop-dev-ro
aws ec2 describe-vpcs --profile shop-dev-ro --query 'Vpcs[].VpcId' # được (ReadOnly)
aws ec2 describe-vpcs --profile shop-dev --query 'Vpcs[].VpcId' # được: boundary cho ec2:Describe*,
# nhưng identity policy không có → AccessDenied
Dòng cuối minh họa đúng luật: boundary cho phép chưa đủ, identity policy cũng phải cho.
Bước 6: Policy Simulator trước khi chạy thật.
# Hỏi: shop-dev gọi được gì?
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::${ACCOUNT_ID}:user/shop-dev \
--action-names s3:ListAllMyBuckets ec2:DescribeVpcs iam:CreateUser sts:AssumeRole \
--profile shop-lab \
--query 'EvaluationResults[].{action:EvalActionName,decision:EvalDecision}' --output table
# s3:ListAllMyBuckets allowed
# ec2:DescribeVpcs implicitDeny ← khớp với bước 5
# iam:CreateUser implicitDeny
# sts:AssumeRole allowed (resource "*" trong simulate; thực tế chỉ ShopReadOnlyRole)
# Hỏi: nếu request đến từ us-east-1 thì boundary có chặn s3 không?
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::${ACCOUNT_ID}:user/shop-dev \
--action-names s3:ListAllMyBuckets \
--context-entries "ContextKeyName=aws:RequestedRegion,ContextKeyValues=us-east-1,ContextKeyType=string" \
--profile shop-lab \
--query 'EvaluationResults[].{action:EvalActionName,decision:EvalDecision,byBoundary:PermissionsBoundaryDecisionDetail.AllowedByPermissionsBoundary}'
# decision: implicitDeny, byBoundary: false ← boundary chặn vì sai region
Console: IAM → Policy simulator (link ở trang Users/Roles), chọn shop-dev, chọn service/action, mục Global Settings điền aws:RequestedRegion.
Bước 7: Boundary chặn privilege escalation. Đóng vai dev, cố tạo role admin.
cat > trust-lambda.json <<'EOF'
{ "Version": "2012-10-17",
"Statement": [{ "Effect": "Allow", "Principal": { "Service": "lambda.amazonaws.com" },
"Action": "sts:AssumeRole" }] }
EOF
# 7a. Không gắn boundary → bị chặn bởi điều kiện iam:PermissionsBoundary
aws iam create-role --role-name shop-dev-resize-role \
--assume-role-policy-document file://trust-lambda.json --profile shop-dev
# AccessDenied ... iam:CreateRole ... with an explicit deny? không: "no identity-based policy allows"
# (điều kiện không khớp nên statement Allow không áp dụng)
# 7b. Gắn boundary → được
aws iam create-role --role-name shop-dev-resize-role \
--assume-role-policy-document file://trust-lambda.json \
--permissions-boundary ${BOUNDARY_ARN} \
--tags Key=Project,Value=shop-app Key=Env,Value=lab --profile shop-dev
# 7c. Dev cố gắn AdministratorAccess cho role mới: lệnh THÀNH CÔNG...
aws iam attach-role-policy --role-name shop-dev-resize-role \
--policy-arn arn:aws:iam::aws:policy/AdministratorAccess --profile shop-dev
# 7d. ...nhưng quyền hiệu lực của role = AdministratorAccess ∩ boundary
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::${ACCOUNT_ID}:role/shop-dev-resize-role \
--action-names iam:CreateUser ec2:RunInstances s3:GetObject \
--profile shop-lab \
--query 'EvaluationResults[].{action:EvalActionName,decision:EvalDecision,byBoundary:PermissionsBoundaryDecisionDetail.AllowedByPermissionsBoundary}' --output table
# iam:CreateUser implicitDeny byBoundary false
# ec2:RunInstances implicitDeny byBoundary false
# s3:GetObject allowed byBoundary true
# 7e. Dev cố gỡ boundary khỏi role: bị Deny tường minh
aws iam delete-role-permissions-boundary --role-name shop-dev-resize-role --profile shop-dev
# AccessDenied ... with an explicit deny in an identity-based policy
Ba thông điệp AccessDenied khác nhau ở 7a, bước 5 và 7e: "no identity-based policy allows" (implicit), "permissions boundary" (sẽ thấy nếu gọi API thật bằng role), "explicit deny". Đọc cụm cuối của lỗi là biết đang vướng lớp nào.
Bước 8: Validate policy bằng Access Analyzer.
cat > bad-policy.json <<'EOF'
{ "Version": "2012-10-17",
"Statement": [{ "Effect": "Allow", "Action": ["s3:GetObjet", "iam:*"], "Resource": "*" }] }
EOF
aws accessanalyzer validate-policy --policy-document file://bad-policy.json \
--policy-type IDENTITY_POLICY --profile shop-lab \
--query 'findings[].{type:findingType,code:issueCode}' --output table
# ERROR INVALID_ACTION (s3:GetObjet gõ sai)
# SECURITY_WARNING PASS_ROLE_WITH_STAR_IN_RESOURCE (iam:* với Resource *)
Console: khi tạo policy bằng JSON, tab bên phải tự hiện các finding này.
Kiểm tra kết quả
aws sts get-caller-identity --profile shop-dev-ro --query Arn --output text | grep -q assumed-role/ShopReadOnlyRole && echo "assume OK"
aws iam get-user --user-name shop-dev --profile shop-lab --query 'User.PermissionsBoundary.PermissionsBoundaryArn'
aws iam get-role --role-name shop-dev-resize-role --profile shop-lab --query 'Role.PermissionsBoundary.PermissionsBoundaryArn'
# cả hai in ra arn:...:policy/ShopDeveloperBoundary
aws iam list-attached-role-policies --role-name shop-dev-resize-role --profile shop-lab # AdministratorAccess, nhưng vô hại
Thử thêm
- Session policy.
aws sts assume-role ... --policy file://only-list.jsonvới policy chỉ chos3:ListAllMyBuckets: phiên nhận được không gọi đượcec2 describe-vpcsdù role là ReadOnly. Lớp ⑥ trong thuật toán. - Role chaining. Dùng profile
shop-dev-rolàmsource_profilecho một profile khác assumeShopAdminRole(sẽ fail vì trust không cho; sửa trust thử nghiệm rồi nhớ khôi phục). Dù role cho 12 giờ, chuỗi chỉ được tối đa 1 giờ. - ABAC. Thêm vào inline của shop-dev: Allow
s3:GetObjectvới"Condition": {"StringEquals": {"aws:ResourceTag/Team": "${aws:PrincipalTag/Team}"}}. Tạo bucket tagTeam=shopvà một bucket tagTeam=other(S3 miễn phí ở mức này), so sánh kết quả. - Access Analyzer policy generation. Console: IAM → Roles → ShopReadOnlyRole → Generate policy based on CloudTrail events (cần CloudTrail trail ghi vào S3, bài 22.15); xem policy sinh ra hẹp hơn ReadOnlyAccess thế nào.
Dọn tài nguyên
Không có gì tính tiền, nhưng dọn để IAM gọn (giữ lại ShopDeveloperBoundary và shop-dev nếu muốn dùng ở các bài sau; còn lại xóa). Thứ tự: role dev tạo → role read-only → user (key, inline policy) → boundary.
aws iam detach-role-policy --role-name shop-dev-resize-role --policy-arn arn:aws:iam::aws:policy/AdministratorAccess --profile shop-lab
aws iam delete-role --role-name shop-dev-resize-role --profile shop-lab
aws iam detach-role-policy --role-name ShopReadOnlyRole --policy-arn arn:aws:iam::aws:policy/ReadOnlyAccess --profile shop-lab
aws iam delete-role --role-name ShopReadOnlyRole --profile shop-lab
for k in $(aws iam list-access-keys --user-name shop-dev --query 'AccessKeyMetadata[].AccessKeyId' --output text --profile shop-lab); do
aws iam delete-access-key --user-name shop-dev --access-key-id $k --profile shop-lab
done
aws iam delete-user-policy --user-name shop-dev --policy-name ShopDevInline --profile shop-lab
aws iam delete-user-permissions-boundary --user-name shop-dev --profile shop-lab
aws iam delete-user --user-name shop-dev --profile shop-lab
aws iam delete-policy --policy-arn ${BOUNDARY_ARN} --profile shop-lab
# xóa profile shop-dev, shop-dev-ro trong ~/.aws/config và ~/.aws/credentials
aws resourcegroupstaggingapi get-resources --tag-filters Key=Project,Values=shop-app --profile shop-lab --query 'ResourceTagMappingList[].ResourceARN'
Phỏng vấn và đề thi SAA hỏi về policy evaluation, cross-account, permission boundary và SCP thì sao?
Q1. IAM đánh giá một request theo thứ tự nào?
🇻🇳 Tiếng Việt:
Mặc định là implicit deny. Đầu tiên nếu có Deny tường minh ở bất kỳ policy nào thì từ chối ngay. Sau đó request phải lọt qua SCP của Organizations nếu có. Trong cùng account, chỉ cần resource-based policy hoặc identity-based policy có Allow là được; nhưng Allow từ identity policy còn phải nằm trong permission boundary và session policy nếu có. Cross-account thì cả bên gọi và bên có resource đều phải Allow.
🇬🇧 English:
The default is an implicit deny. First, an explicit Deny in any applicable policy wins immediately. Then the request must be allowed by the Organizations SCP if one applies. Within the same account, an Allow in either the resource-based policy or the identity-based policy is enough, but an identity-based Allow must also fall inside the permissions boundary and any session policy. For cross-account access both the caller's identity policy and the resource owner's policy must allow it.
Q2. Cho một account khác truy cập S3 bucket của bạn: bạn làm thế nào và vì sao cần ExternalId?
🇻🇳 Tiếng Việt:
Có hai cách. Một là bucket policy cho principal của account kia, bên họ gắn identity policy tương ứng; giữ nguyên danh tính bên gọi và không cần đổi vai. Hai là tạo role ở account em với quyền đọc bucket, trust policy cho account họ assume; cách này dùng được với mọi dịch vụ và là một điểm kiểm soát duy nhất. Với bên thứ ba như SaaS, em thêm điều kiện sts:ExternalId vào trust policy để tránh confused deputy: bên thứ ba truyền ID riêng của khách hàng, kẻ xấu không thể mượn vendor để assume role của em.
🇬🇧 English:
There are two options. One is a bucket policy granting the other account's principal, with a matching identity policy on their side; it keeps the caller's identity and needs no role switch. The other is a role in my account with read access to the bucket and a trust policy letting their account assume it; this works for every service and gives a single point of control. For a third party such as a SaaS vendor I add an sts:ExternalId condition to the trust policy to prevent the confused deputy problem: the vendor passes a per-customer ID, so an attacker cannot trick the vendor into assuming my role.
Q3. Permission boundary khác SCP ở đâu? Mỗi cái dùng cho tình huống gì?
🇻🇳 Tiếng Việt:
Cả hai đều chỉ giới hạn, không cấp quyền. Permission boundary là managed policy gắn vào một user hay role trong account, do admin account quản; dùng để ủy quyền tạo IAM cho developer mà không cho leo thang quyền, bằng điều kiện iam:PermissionsBoundary. SCP gắn vào OU hoặc account trong Organizations, do management account quản, trói cả admin và root của account con; dùng làm guardrail toàn tổ chức như khóa region hay cấm dịch vụ.
🇬🇧 English:
Both only restrict; neither grants permissions. A permissions boundary is a managed policy attached to a single user or role inside an account and managed by that account's admins; it is used to delegate IAM creation to developers without privilege escalation, via the iam:PermissionsBoundary condition. An SCP is attached to an OU or account in Organizations, managed by the management account, and it constrains even the admins and the root user of member accounts; it is used for organization-wide guardrails such as region restrictions or forbidden services.
Q4. Khi nào bạn gọi STS trong code Java, và khi nào không?
🇻🇳 Tiếng Việt:
Thường là không: SDK v2 dùng default credentials chain, trên EC2 hay ECS nó tự lấy credential tạm của role gắn vào máy và tự refresh. Em chỉ gọi STS tường minh khi cần đổi sang role khác, ví dụ ghi vào SQS của account đối tác; khi đó dùng StsAssumeRoleCredentialsProvider với roleArn, externalId và sessionName có ý nghĩa, provider tự cache và refresh trước khi hết hạn.
🇬🇧 English:
Usually I don't. The SDK v2 default credentials chain picks up the temporary credentials of the role attached to the EC2 instance or ECS task and refreshes them automatically. I call STS explicitly only when I need to switch to another role, for example to write to a partner account's SQS queue; then I use StsAssumeRoleCredentialsProvider with the roleArn, externalId and a meaningful session name, and the provider caches and refreshes the credentials before they expire.
Góc thi SAA-C03
Câu 1: Một công ty dùng AWS Organizations với nhiều account. Nhóm bảo mật yêu cầu KHÔNG account nào được tạo tài nguyên ngoài region ap-southeast-1, kể cả khi admin của account đó có AdministratorAccess. Giải pháp nào đáp ứng với ÍT công việc vận hành nhất?
A. Gắn permission boundary có điều kiện aws:RequestedRegion vào mọi IAM user và role trong từng account.
B. Tạo SCP với Deny mọi action khi aws:RequestedRegion khác ap-southeast-1 (chừa dịch vụ global), gắn vào OU chứa các account.
C. Dùng AWS Config rule phát hiện tài nguyên ngoài region và Lambda tự động xóa.
D. Viết IAM policy Deny trong từng account và gắn vào mọi user bằng script chạy hằng ngày.
Đáp án: B. SCP áp lên cả account, trói được admin và root, quản lý tập trung ở một chỗ. A: boundary phải gắn từng principal, admin gỡ được, không tập trung. C: phát hiện sau khi đã tạo, không ngăn chặn, tốn thêm vận hành. D: admin trong account tự gỡ được, script là việc thừa.
Câu 2: Một công ty thuê nhà cung cấp SaaS giám sát hạ tầng. Nhà cung cấp cần đọc metric CloudWatch trong account của công ty. Solutions architect phải thiết kế truy cập cross-account AN TOÀN NHẤT.
A. Tạo IAM user cho nhà cung cấp, gửi access key qua email mã hóa.
B. Tạo IAM role với quyền đọc CloudWatch, trust policy cho account của nhà cung cấp kèm điều kiện sts:ExternalId do nhà cung cấp cấp riêng cho công ty.
C. Tạo IAM role với quyền đọc CloudWatch, trust policy principal * để nhà cung cấp dễ tích hợp.
D. Bật CloudWatch cross-account observability và chia sẻ dữ liệu bằng resource policy cho principal *.
Đáp án: B. Role cross-account với ExternalId là mẫu chuẩn cho bên thứ ba, ngăn confused deputy, credential tạm, thu hồi bằng sửa trust policy. A: credential dài hạn gửi ra ngoài. C và D: principal * là mở cho cả thế giới.
Câu 3: Developer trong một account cần tự tạo IAM role cho các Lambda function của họ. Nhóm bảo mật lo developer tạo role có quyền cao hơn chính họ. Giải pháp nào ngăn leo thang quyền mà vẫn cho developer tự phục vụ?
A. Chỉ cho developer quyền iam:PassRole, mọi role do nhóm bảo mật tạo sẵn.
B. Tạo permission boundary policy; cho developer iam:CreateRole và iam:AttachRolePolicy với điều kiện iam:PermissionsBoundary bằng ARN của boundary đó; Deny các action xóa hoặc sửa boundary.
C. Dùng SCP cấm iam:CreateRole cho toàn OU.
D. Cho developer AdministratorAccess nhưng bật CloudTrail để truy vết.
Đáp án: B. Đúng mẫu "delegate IAM safely": role mới bắt buộc gắn boundary nên quyền hiệu lực không vượt trần. A: không tự phục vụ, nhóm bảo mật thành nút thắt. C: cấm hẳn, không đáp ứng yêu cầu self-service. D: không ngăn gì, chỉ ghi lại.
Cách nhớ ngắn gọn
- Thuật toán: Deny tường minh → SCP → resource Allow (cùng account là đủ) → identity Allow → boundary → session.
- Cùng account: một bên Allow. Khác account: cả hai bên Allow. SSE-KMS: thêm key policy, ba bên.
- Boundary và SCP là trần, không cấp quyền. Boundary: một principal, admin account. SCP: cả account, trói cả root.
- Bên thứ ba assume role: ExternalId chống confused deputy. Role chaining: tối đa 1 giờ.
- Debug: Policy Simulator (trước), Access Analyzer (lint/audit), CloudTrail (sau).
| Từ khóa trong đề | Nghĩ đến |
|---|---|
| prevent any account / even administrators / restrict regions | SCP trong Organizations |
| developers create roles without escalating privileges | Permission boundary + iam:PermissionsBoundary condition |
| third-party / SaaS vendor assumes role | Cross-account role + sts:ExternalId |
| another account reads bucket, retain identity, no role switch | Bucket policy + identity policy bên gọi |
| mobile app / GitHub Actions / EKS pod gets AWS credentials | AssumeRoleWithWebIdentity (Cognito, OIDC provider, IRSA) |
| resources shared publicly or with external accounts | IAM Access Analyzer |
| bucket owner cannot read objects uploaded by other account | S3 Object Ownership: Bucket owner enforced |
Tự kiểm tra
Role có AdministratorAccess nhưng bị AccessDenied khi đọc S3. Khả năng cao nhất là gì?
Có Deny tường minh ở đâu đó: bucket policy (thường kèm điều kiện aws:SourceVpce, aws:SecureTransport), SCP, hoặc permission boundary. Explicit deny thắng mọi Allow.
Cross-account: account B đọc bucket ở account A cần những Allow nào?
Identity policy của principal ở B cho s3:GetObject trên bucket của A, và bucket policy ở A cho principal của B. Nếu bucket SSE-KMS, thêm key policy cho B và kms:Decrypt ở B.
ExternalId giải quyết vấn đề gì?
Confused deputy: bên thứ ba được nhiều khách hàng trust có thể bị kẻ xấu lợi dụng để assume role của khách khác. ExternalId riêng cho từng khách, truyền khi AssumeRole và kiểm tra trong trust policy, ngăn điều đó.
Dev gắn AdministratorAccess vào role có permission boundary hẹp. Role được làm gì?
Chỉ phần giao giữa AdministratorAccess và boundary, tức đúng những gì boundary cho. Boundary là trần; policy gắn thêm không vượt được.
Vì sao SCP khóa region phải dùng NotAction chừa IAM, STS, Route 53, CloudFront…?
Đó là dịch vụ global, request của chúng có aws:RequestedRegion là us-east-1 hoặc không khớp region bạn cho phép. Không chừa thì khóa luôn đăng nhập, assume role, DNS.
🧪 Thực hành bài này: Basic: Nền tảng: IAM + VPC + EC2 (chưa triển khai) · lab labs/aws