Bài 2 dạy IAM cơ bản — policy, user, role, permission boundary. Bài này đi vào những thứ bạn cần khi team > 5 người và có > 1 AWS account: cross-account access, ABAC (Attribute-Based Access Control), SCP (Service Control Policy), và IAM Identity Center.

Nếu Bài 2 là “IAM trong 1 account”, Bài 3 là “IAM cho organization nhiều account”.


  flowchart TB
    subgraph Prod["Production Account (11111111)"]
        AdminRole["Admin Role<br/>trusted by Staging"]
    end
    subgraph Staging["Staging Account (22222222)"]
        DeployRole["Deploy Role<br/>trust: AdminRole@Prod<br/>condition: ExternalId"]
    end
    subgraph Security["Security Account"]
        AuditRole["Audit Role<br/>read-only all accounts<br/>trusted by Org"]
    end
    subgraph Org["AWS Organization"]
        SCP["SCP: Deny root, restrict regions, deny public S3"]
        IDC["IAM Identity Center<br/>SSO cho toàn Org"]
    end
    AdminRole -->|"AssumeRole + ExternalId"| DeployRole
    AuditRole -->|"AssumeRole"| Prod
    AuditRole -->|"AssumeRole"| Staging
    SCP -.-> Prod
    SCP -.-> Staging
    IDC --> Prod
    IDC --> Staging

Cross-account access: dual authorization

Cross-account access cần cả hai bên cùng cho phép (dual authorization):

  1. Trust policy ở account ĐÍCH — cho phép principal từ account NGUỒN assume role này
  2. Identity policy ở account NGUỒN — cho phép user/role gọi sts:AssumeRole đến role ĐÍCH

Thiếu một trong hai → AccessDenied.

# ===== ACCOUNT ĐÍCH (Staging 222222222222): Tạo role =====
aws iam create-role \
  --role-name deploy-role \
  --assume-role-policy-document '{
    "Version": "2012-10-17",
    "Statement": [{
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::111111111111:role/admin"
      },
      "Action": "sts:AssumeRole",
      "Condition": {
        "StringEquals": {
          "sts:ExternalId": "staging-deploy-2026-06"
        }
      }
    }]
  }'

aws iam attach-role-policy \
  --role-name deploy-role \
  --policy-arn arn:aws:iam::aws:policy/AdministratorAccess

# ===== ACCOUNT NGUỒN (Production 111111111111): Cho phép assume =====
aws iam put-role-policy \
  --role-name admin \
  --policy-name allow-staging-deploy \
  --policy-document '{
    "Version": "2012-10-17",
    "Statement": [{
      "Effect": "Allow",
      "Action": "sts:AssumeRole",
      "Resource": "arn:aws:iam::222222222222:role/deploy-role"
    }]
  }'
// TypeScript SDK: assume role cross-account
import { STSClient, AssumeRoleCommand } from "@aws-sdk/client-sts";

const sts = new STSClient({ region: "ap-southeast-1" });

const { Credentials } = await sts.send(
  new AssumeRoleCommand({
    RoleArn: "arn:aws:iam::222222222222:role/deploy-role",
    RoleSessionName: `deploy-${Date.now()}`,
    ExternalId: "staging-deploy-2026-06",
    DurationSeconds: 3600,
  })
);

const s3 = new S3Client({
  credentials: {
    accessKeyId: Credentials!.AccessKeyId!,
    secretAccessKey: Credentials!.SecretAccessKey!,
    sessionToken: Credentials!.SessionToken!,
  },
});

Confused Deputy Problem & ExternalId

Vấn đề

Attacker cũng có AWS account. Họ biết role ARN của bạn (không phải secret — ARN có thể xuất hiện trong error message, documentation, hoặc guess được). Nếu trust policy của bạn dùng Principal: "*" hoặc quá rộng, attacker có thể:

  1. Tạo IAM user trong account của họ
  2. Gọi sts:AssumeRole đến role của bạn
  3. Nếu không có ExternalId condition → thành công

Đây gọi là Confused Deputy Attack — attacker lợi dụng một service (STS) có quyền cao để thực hiện hành động họ không được phép.

Giải pháp: ExternalId

ExternalId là shared secret giữa hai bên — một string unique mà chỉ bên hợp pháp biết:

"Condition": {
  "StringEquals": {
    "sts:ExternalId": "customer-abc123-unique-id"
  }
}

Attacker không thể đoán được ExternalId → AssumeRole thất bại.

# Gọi AssumeRole với ExternalId
aws sts assume-role \
  --role-arn arn:aws:iam::222222222222:role/deploy-role \
  --role-session-name test \
  --external-id "customer-abc123-unique-id"

Best practices 2025-2026

ScenarioDùng
Third-party/vendor truy cập account bạnExternalId — unique GUID per customer
Internal cross-account (cùng Organization)aws:PrincipalOrgID — đơn giản hơn, không cần shared secret
Service role (Lambda, EC2)aws:SourceArn + aws:SourceAccount — chống cross-service confused deputy
// Trust policy cho internal cross-account (dùng PrincipalOrgID)
{
  "Effect": "Allow",
  "Principal": { "AWS": "arn:aws:iam::111111111111:root" },
  "Action": "sts:AssumeRole",
  "Condition": {
    "StringEquals": {
      "aws:PrincipalOrgID": "o-abc123example"
    }
  }
}
Role chaining có hard cap 1 giờ. Khi bạn assume role A → dùng credential đó assume role B, session của B không thể > 1 giờ, bất kể MaxSessionDuration của role B là 12 giờ. Đây là STS hard limit. Nếu cần session dài hơn, assume trực tiếp từ IAM user hoặc dùng GetSessionToken.

ABAC: Attribute-Based Access Control

Thay vì tạo 10 role cho 10 team, ABAC dùng tag để quyết định quyền. Pattern 2025: 1 role + session tags = nhiều mức permission khác nhau.

Trust policy: kiểm soát ai được set tag gì

{
  "Sid": "AllowAssumeWithTeamTag",
  "Effect": "Allow",
  "Principal": {"AWS": "arn:aws:iam::111111111111:role/admin"},
  "Action": "sts:AssumeRole",
  "Condition": {
    "StringEquals": {"aws:RequestTag/Team": ["backend", "frontend", "data"]}
  }
},
{
  "Sid": "AllowTagSession",
  "Effect": "Allow",
  "Principal": {"AWS": "arn:aws:iam::111111111111:role/admin"},
  "Action": "sts:TagSession",
  "Condition": {
    "StringEquals": {"aws:RequestTag/Team": ["backend", "frontend", "data"]},
    "ForAllValues:StringEquals": {"aws:TagKeys": "Team"}
  }
}

Permission policy: check session tag

{
  "Effect": "Allow",
  "Action": ["ec2:StartInstances", "ec2:StopInstances"],
  "Resource": "*",
  "Condition": {
    "StringEquals": {
      "aws:ResourceTag/Team": "${aws:PrincipalTag/Team}"
    }
  }
}

AssumeRole với session tag

aws sts assume-role \
  --role-arn arn:aws:iam::222222222222:role/app-role \
  --role-session-name backend-deploy \
  --tags Key=Team,Value=backend

Kết quả: user chỉ start/stop được EC2 instance có tag Team=backend. Một role, nhiều team, permission quyết định bởi tag.

RBAC vs ABAC

RBACABAC
ScaleTốt cho <5 teamTốt cho 50+ team
AuditDễ — mỗi team một roleKhó hơn — permission phụ thuộc tag
Add team mớiTạo role mới + policy mớiChỉ cần tag mới
Pattern 2025Base permission (S3, Lambda)Resource-level control (EC2 của team mình)

SCP: Service Control Policy — firewall Organization

SCP là trần quyền tối đa cho mọi principal trong account. Nó KHÔNG cấp quyền — nó chỉ giới hạn.

Top 10 SCP mọi Organization nên có

1. Deny root user actions (tất cả OU trừ management)

{
  "Sid": "DenyRoot",
  "Effect": "Deny",
  "Action": "*",
  "Resource": "*",
  "Condition": { "StringLike": { "aws:PrincipalArn": "arn:aws:iam::*:root" } }
}

2. Region restriction

{
  "Sid": "DenyUnapprovedRegions",
  "Effect": "Deny",
  "NotAction": [
    "iam:*",
    "organizations:*",
    "cloudfront:*",
    "route53:*",
    "sts:*",
    "support:*"
  ],
  "Resource": "*",
  "Condition": {
    "StringNotEquals": {
      "aws:RequestedRegion": ["ap-southeast-1", "us-east-1"]
    }
  }
}

3. Deny public S3 access (ACL + Bucket Policy + Object ACL)

{
  "Sid": "DenyPublicReadACL",
  "Effect": "Deny",
  "Action": "s3:PutBucketAcl",
  "Resource": "*",
  "Condition": { "StringEquals": { "s3:x-amz-acl": "public-read" } }
}
{
  "Sid": "DenyPublicBucketPolicy",
  "Effect": "Deny",
  "Action": "s3:PutBucketPolicy",
  "Resource": "*",
  "Condition": { "StringEquals": { "s3:PolicyReadAccess": "public" } }
}
{
  "Sid": "DenyPublicObjectACL",
  "Effect": "Deny",
  "Action": "s3:PutObjectAcl",
  "Resource": "*",
  "Condition": { "StringEquals": { "s3:x-amz-acl": "public-read" } }
}

4. Require IMDSv2

{
  "Sid": "RequireIMDSv2",
  "Effect": "Deny",
  "Action": "ec2:RunInstances",
  "Resource": "arn:aws:ec2:*:*:instance/*",
  "Condition": { "StringNotEquals": { "ec2:MetadataHttpTokens": "required" } }
}
  1. Deny IAM user/access key creation — bắt buộc SSO
  2. Prevent CloudTrail disabling/deletion
  3. Deny leaving Organization
  4. Restrict EC2 instance types (chặn family đắt)
  5. Deny disabling security services (GuardDuty, Security Hub)
  6. Require tags on resources (cho cost allocation)
aws organizations attach-policy --policy-id p-xxx --target-id ou-abc-123

IAM Identity Center: thay thế IAM user

Khi team > 5 người, đừng tạo IAM user riêng lẻ. Dùng Identity Center:

IAM UserIdentity Center
LoginPassword + MFA từng accountMột portal, corporate credential
Multi-accountTạo user từng accountGán user → account + permission set
ProvisioningManualTự động sync Google Workspace/Okta/Azure AD
aws sso-admin create-permission-set \
  --instance-arn arn:aws:sso:::instance/ssoins-xxx \
  --name "BackendDeveloper"

aws sso-admin attach-managed-policy-to-permission-set \
  --permission-set-arn $PS_ARN \
  --managed-policy-arn arn:aws:iam::aws:policy/PowerUserAccess

aws sso-admin create-account-assignment \
  --instance-arn arn:aws:sso:::instance/ssoins-xxx \
  --target-id 222222222222 --target-type AWS_ACCOUNT \
  --permission-set-arn $PS_ARN \
  --principal-type USER --principal-id $USER_UUID

STS API reference

APIUse CaseCredential Source
AssumeRoleCross-account, service roleIAM role (trust policy)
GetSessionTokenCó thể dùng không MFA (session token = IAM user permissions) hoặc MFA (thêm SerialNumber + TokenCode)IAM user ± MFA
GetFederationTokenFederationIAM user
AssumeRoleWithWebIdentityOIDC (GitHub Actions, EKS Pod Identity)Web Identity JWT
AssumeRoleWithSAMLSAML federationSAML assertion

Session duration: AssumeRole cho phép 1-12h (tùy role config). Role chaining luôn cap ở 1h.


Tổng kết

  • Cross-account = dual authorization: trust policy (đích) + identity policy (nguồn)
  • ExternalId cho third-party, aws:PrincipalOrgID cho internal — chống confused deputy
  • ABAC = 1 role + session tags — scale IAM cho 50+ team
  • SCP top 10: deny root, restrict regions, deny public S3, require IMDSv2, deny IAM user creation…
  • Identity Center > IAM user khi team > 5 người
  • STS AssumeRole cho mọi thứ, access key chỉ cho legacy

Bài sau: Phần 4: CLI, SDK TypeScript v3 & credential chain