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):
- Trust policy ở account ĐÍCH — cho phép principal từ account NGUỒN assume role này
- 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ể:
- Tạo IAM user trong account của họ
- Gọi
sts:AssumeRoleđến role của bạn - 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
| Scenario | Dùng |
|---|---|
| Third-party/vendor truy cập account bạn | ExternalId — 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"
}
}
}
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
| RBAC | ABAC | |
|---|---|---|
| Scale | Tốt cho <5 team | Tốt cho 50+ team |
| Audit | Dễ — mỗi team một role | Khó hơn — permission phụ thuộc tag |
| Add team mới | Tạo role mới + policy mới | Chỉ cần tag mới |
| Pattern 2025 | Base 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" } }
}
- Deny IAM user/access key creation — bắt buộc SSO
- Prevent CloudTrail disabling/deletion
- Deny leaving Organization
- Restrict EC2 instance types (chặn family đắt)
- Deny disabling security services (GuardDuty, Security Hub)
- 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 User | Identity Center | |
|---|---|---|
| Login | Password + MFA từng account | Một portal, corporate credential |
| Multi-account | Tạo user từng account | Gán user → account + permission set |
| Provisioning | Manual | Tự độ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
| API | Use Case | Credential Source |
|---|---|---|
AssumeRole | Cross-account, service role | IAM role (trust policy) |
GetSessionToken | Có thể dùng không MFA (session token = IAM user permissions) hoặc MFA (thêm SerialNumber + TokenCode) | IAM user ± MFA |
GetFederationToken | Federation | IAM user |
AssumeRoleWithWebIdentity | OIDC (GitHub Actions, EKS Pod Identity) | Web Identity JWT |
AssumeRoleWithSAML | SAML federation | SAML 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:PrincipalOrgIDcho 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