IAM sâu: policy evaluation, STS AssumeRole, resource policy, permission boundary, Organizations & SCP

6 câu hỏi~50 phútTừ khóa: policy evaluation logic, explicit deny, identity-based vs resource-based, STS AssumeRole, ExternalId, confused deputy, permission boundary, Organizations, OU, SCP, Condition key, policy simulator, IAM Access Analyzer · SAA: Domain 3 Secure

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

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:

  1. Explicit Deny thắng tất cả. Lỗi của bạn ở bucket policy thuộc loại này.
  2. Mặc định là implicit deny; phải có Allow mới được.
  3. 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ớpGắn vàoCấp quyền?Ai quảnTừ khóa đề
Identity-based policyUser, group, roleCóAdmin account"grant permissions to the role"
Resource-based policyS3 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 boundaryUser, role (một managed policy)Không, chỉ giới hạnAdmin account"allow developers to create roles but not exceed their own permissions", "delegate IAM safely"
SCP (Service Control Policy)Root, OU, account trong OrganizationsKhông, chỉ giới hạn; ảnh hưởng cả root user của account conManagement account"prevent all accounts from…", "restrict regions", "guardrail"
Session policyTruyền khi AssumeRole / GetFederationTokenKhông, chỉ giới hạn phiênNgười gọi"further restrict temporary credentials"
ACL (S3, legacy)Bucket/objectCó, nhưng khuyến nghị tắtChủ objectThườ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ĩaVí dụ dùng
aws:RequestedRegionRegion của API callKhóa region (SCP)
aws:SourceIpIP public của người gọiChỉ 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:SourceVpcVPC endpoint / VPC nguồnBucket policy: chỉ nhận request qua endpoint của shop-vpc (chính là Deny làm bạn vấp)
aws:MultiFactorAuthPresentPhiên có MFATrust policy ShopAdminRole (bài 22.1)
aws:PrincipalTag/Team, aws:ResourceTag/EnvTag của người gọi / của resourceABAC: user tag Team=shop chỉ sửa resource tag Team=shop
aws:PrincipalOrgIDOrganization ID của người gọiBucket policy: cho mọi account trong tổ chức, không cần liệt kê
aws:SecureTransportRequest qua TLS?Deny nếu false (bắt buộc HTTPS vào S3)
sts:ExternalIdChuỗi bí mật bên thứ ba truyền khi AssumeRoleChống confused deputy
iam:PermissionsBoundaryBoundary gắn khi tạo user/roleBắ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):

APIAi gọiKhi nào
AssumeRolePrincipal IAM (user/role) đã có credential AWSCross-account, nâng quyền, EC2/ECS/Lambda bên dưới cũng dùng cái này
AssumeRoleWithWebIdentityMang JWT từ OIDC provider (Cognito, Google, GitHub Actions, EKS IRSA)Mobile app, CI không có key AWS, pod trong EKS
AssumeRoleWithSAMLMang SAML assertion từ IdP doanh nghiệp (AD FS, Okta)Federation SSO kiểu cũ; nay thường qua IAM Identity Center
GetSessionTokenIAM user, kèm MFALấy credential tạm có MFA cho chính user (không đổi vai)
GetFederationTokenProxy 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ặpKhác nhau cốt lõiChọn khi
Identity-based vs Resource-basedGắn vào ai gọi vs gắn vào thứ bị gọi; resource-based có PrincipalResource-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 SCPBoundary: một principal, trong account, admin account quản. SCP: cả account/OU, management account quản, ảnh hưởng rootBoundary: ủ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 providerWeb identity cho mobile (Cognito), GitHub Actions, EKS pod
RBAC vs ABACRole theo chức danh, nhiều policy vs vài policy dùng tag điều kiệnABAC khi team/project mọc nhanh, tag chuẩn hóa tốt
IAM Access Analyzer vs Policy SimulatorTì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 NACLKhá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

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ọiGiữ danh tính của BTrở thành role của A
Gọi nhiều accountDễ: một credential đọc bucket của nhiều accountPhải đổi credential từng account
Dịch vụ không có resource policy (EC2, RDS, hầu hết)Không dùng đượcCách duy nhất
Thu hồiSửa policy từng resourceSửa một trust policy
Object ownership (S3 ghi vào)Cần Bucket owner enforced để A sở hữu object B ghiObject 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 boundarySCP
Phạm viMột user/role cụ thểMọi principal trong account/OU, kể cả root
Ai đặtAdmin trong account (ai có iam:PutRolePermissionsBoundary)Management account của Organizations
Ai bịChỉ principal được gắn; admin gỡ đượcCả 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ứcMột managed policy bình thườngTài liệu JSON riêng, không có Principal, không hỗ trợ mọi Condition
Yêu cầuKhông cần OrganizationsCầ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

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

Chi phí & thời gian

Chuẩn bị

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

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

Từ khóa trong đềNghĩ đến
prevent any account / even administrators / restrict regionsSCP trong Organizations
developers create roles without escalating privilegesPermission boundary + iam:PermissionsBoundary condition
third-party / SaaS vendor assumes roleCross-account role + sts:ExternalId
another account reads bucket, retain identity, no role switchBucket policy + identity policy bên gọi
mobile app / GitHub Actions / EKS pod gets AWS credentialsAssumeRoleWithWebIdentity (Cognito, OIDC provider, IRSA)
resources shared publicly or with external accountsIAM Access Analyzer
bucket owner cannot read objects uploaded by other accountS3 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