Definition
- IAM (Identity and Access Management) – The AWS service that controls authentication (who are you) and authorization (what may you do) for every request to every AWS service.
- IAM User – A permanent identity with long-lived credentials (password and/or access keys) for a human or legacy application.
- IAM Group – A collection of Users that share the same policies, used only for convenience; you never log in as a Group.
- IAM Role – An identity with no long-lived credentials, assumed temporarily through STS to obtain short-lived credentials that expire.
- IAM Policy – A JSON document listing allowed or denied actions on specific resources, attached to Users, Groups, or Roles to grant permissions.
At National Bank of India, an IAM User is a permanent staff member like Rajesh with his own ID card and password. An IAM Group is a department like Tellers – everyone in it automatically gets the teller permissions. An IAM Role is a temporary visitor badge: a contractor or a machine borrows it for a few hours, does the job, and the badge expires. An IAM Policy is the printed rulebook that says exactly which rooms a badge can open – may open the cash vault, may not open the manager's office.
Understanding the Concepts
What is IAM?
IAM (Identity and Access Management) is the AWS service that answers two questions for every single API request: who are you (authentication), and what are you allowed to do (authorization). It is a global service, meaning you create Users, Groups, Roles, and Policies once and they work across all Regions. IAM itself is free; you pay for the AWS resources the identities access.
At National Bank of India, IAM is how we control which engineer can restart the settlement server, which service can read the customer-statements bucket, and which account can assume a cross-account analytics role.
import software.amazon.awssdk.services.iam.IamClient;
import software.amazon.awssdk.services.iam.model.GetUserRequest;
public class WhoAmI {
public static void main(String[] args) {
IamClient iam = IamClient.create();
var user = iam.getUser(GetUserRequest.builder().build()).user();
System.out.println("Authenticated as: " + user.userName());
System.out.println("ARN: " + user.arn());
}
}
What is an IAM User?
An IAM User represents one identity with durable credentials. You give Rajesh a User with a console password for the web UI and optionally an access key ID and secret for the CLI or SDK. The credentials are long-lived: the password exists until changed, and access keys exist until rotated or deleted.
The problem with Users for applications is that those access keys are permanent secrets. If they leak through a Git commit, a Docker image, or a log file, an attacker can use them until someone manually rotates them. That is why we avoid Users for machines and prefer Roles.
import software.amazon.awssdk.services.iam.IamClient;
import software.amazon.awssdk.services.iam.model.CreateUserRequest;
import software.amazon.awssdk.services.iam.model.CreateLoginProfileRequest;
public class CreateBankUser {
public static void main(String[] args) {
IamClient iam = IamClient.create();
// Create a permanent user for engineer Rajesh
iam.createUser(CreateUserRequest.builder()
.userName("rajesh-teller").build());
// Set console password
iam.createLoginProfile(CreateLoginProfileRequest.builder()
.userName("rajesh-teller")
.password("TempP@ss123!") // must change on first login
.passwordResetRequired(true).build());
}
}
What is an IAM Group?
An IAM Group exists purely for convenience. You never log in as a Group. You create a Group called Developers, attach the policies once, and add Users to it. When a new engineer joins, you add them to the Group and they instantly inherit the right permissions. Remove them from the Group and the permissions disappear. A User can belong to multiple Groups.
import software.amazon.awssdk.services.iam.IamClient;
import software.amazon.awssdk.services.iam.model.*;
public class GroupManagement {
public static void main(String[] args) {
IamClient iam = IamClient.create();
// Create the Developers group
iam.createGroup(CreateGroupRequest.builder()
.groupName("NBI-Developers").build());
// Add Rajesh to the group
iam.addUserToGroup(AddUserToGroupRequest.builder()
.groupName("NBI-Developers")
.userName("rajesh-teller").build());
// Attach a policy to the group – all members get it
iam.attachGroupPolicy(AttachGroupPolicyRequest.builder()
.groupName("NBI-Developers")
.policyArn("arn:aws:iam::123456789012:policy/ReadStatements")
.build());
}
}
What is an IAM Role?
An IAM Role is the modern, secure way to grant permissions. A Role has no password and no permanent access keys. Instead, a trusted principal assumes the Role through STS (Security Token Service) and receives temporary credentials that expire, typically in an hour. An EC2 instance, a Lambda function, or a user from another account can assume a Role.
The Role has two policies: a trust policy saying who is allowed to assume it, and permission policies saying what it can do once assumed.
import software.amazon.awssdk.services.sts.StsClient;
import software.amazon.awssdk.services.sts.model.AssumeRoleRequest;
public class AssumeAnalyticsRole {
public static void main(String[] args) {
StsClient sts = StsClient.create();
var response = sts.assumeRole(AssumeRoleRequest.builder()
.roleArn("arn:aws:iam::123456789012:role/StatementReaderRole")
.roleSessionName("rajesh-analytics-session")
.build());
var creds = response.credentials();
System.out.println("Temporary key: " + creds.accessKeyId());
System.out.println("Expires: " + creds.expiration());
// These credentials expire – far safer than embedded keys
}
}
What is an IAM Policy?
An IAM Policy is the JSON document that actually decides allow or deny. It is attached to a User, Group, or Role. The policy has a Version, and one or more Statements, each with an Effect (Allow or Deny), an Action (the API calls), and a Resource (the ARNs). A minimal policy that lets the holder read one S3 bucket of statements looks like this:
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "ReadBankStatements",
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:ListBucket"],
"Resource": [
"arn:aws:s3:::nbi-customer-statements",
"arn:aws:s3:::nbi-customer-statements/*"
]
}]
}
How is a User different from a Role?
| Aspect | IAM User | IAM Role |
|---|---|---|
| Credentials | Permanent password and/or access keys | Temporary credentials from STS, expire in 1-12 hours |
| Intended for | Human operators, legacy apps | Machines (EC2, Lambda), cross-account, federation |
| If credentials leak | Attacker has access until manual rotation | Token expires automatically; damage is time-limited |
| Rotation | Manual; must update every app using the key | Automatic; STS issues new credentials each session |
| Best practice | Use for humans with MFA; avoid for code | Use for all programmatic access |
// WRONG: User access key embedded in code – permanent secret
String accessKey = "AKIAIOSFODNN7EXAMPLE"; // leaked = breach until rotated
String secretKey = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY";
// CORRECT: Role assumed by EC2 – temporary, auto-rotated
// The SDK picks up credentials from the instance metadata automatically
S3Client s3 = S3Client.create(); // no keys in code at all
What are common mistakes?
The most common mistake is giving every developer admin access. This violates least privilege and means a single compromised account can destroy the entire AWS setup. The fix is to create Groups with scoped policies.
The second mistake is embedding access keys in application code. The keys end up in Git history, Docker layers, and log files. The fix is to use Roles for all machine access.
The third mistake is not enabling MFA on User accounts, especially the root account. Without MFA, a leaked password is enough for full access.
public class IAMMistakes {
// WRONG: admin policy on every developer
// { "Effect": "Allow", "Action": "*", "Resource": "*" }
// CORRECT: scoped policy per team
// Tellers: read/write transfers table only
// Auditors: read-only on all tables
// Admins: full access with MFA required
}
How do effective permissions work?
Permissions are the union of everything attached. If Rajesh is a User in the Tellers Group with one policy and also has an inline policy of his own, his effective permissions are all of them combined, minus any explicit deny, which always wins. The evaluation order is: if any policy says Deny, the request is denied; otherwise if any policy says Allow, it is permitted; otherwise it is implicitly denied.
Rajesh (User)
├── Inline policy: Allow s3:GetObject on statements bucket
├── Group: Tellers
│ └── Group policy: Allow dynamodb:GetItem on transfers table
└── Effective permissions:
Allow s3:GetObject on statements bucket (from inline)
Allow dynamodb:GetItem on transfers table (from group)
Everything else: implicitly denied
What should a senior engineer know?
A senior engineer knows that IAM is the single most critical security service in AWS. They use Roles for all programmatic access, enforce MFA on all human Users, and use AWS Organizations service control policies to set guardrails across the entire account structure. They know that the root account should never be used for daily operations and should have MFA with a hardware key stored in a safe.
They also understand that IAM policies are additive, permission boundaries set a ceiling on what a Role can do even if its own policy allows more, and session policies further restrict permissions during an assumed-role session.
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "PermissionBoundaryCeiling",
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": "arn:aws:s3:::nbi-*/*"
}]
}
Visual Overview
┌─────────────────── AWS Account ───────────────────┐ │ │ │ IAM User (Rajesh) │ │ ├── Console password + MFA │ │ ├── Member of Group: NBI-Developers │ │ │ └── Policy: ReadStatements │ │ └── Inline Policy: WriteTransfers │ │ │ │ IAM Role (SettlementServiceRole) │ │ ├── Trust Policy: EC2 can assume this role │ │ └── Permission Policy: ReadWriteStatements │ │ │ │ │ ▼ │ │ EC2 Instance (Settlement Service) │ │ └── Instance Profile → Role │ │ └── SDK auto-fetches temporary credentials │ │ from instance metadata (169.254.169.254) │ │ └── Credentials expire in 1 hour │ │ └── SDK auto-refreshes before expiry │ │ │ │ Policy Evaluation Flow: │ │ Request → Gather all policies │ │ → Explicit Deny? ──YES──→ DENIED │ │ → Explicit Allow? ─YES──→ ALLOWED │ │ → Neither? ─────────────→ DENIED (implicit)│ └────────────────────────────────────────────────────┘
Key Takeaway
IAM is the AWS service that answers two questions for every request: who are you, and what are you allowed to do. The four building blocks are User, Group, Role, and Policy, and the key is understanding which ones are identities and which one is the permission itself. A Policy is the permission – a JSON document that says allow or deny a set of actions on specific resources. It carries no identity on its own; you attach it to something.
A User is a permanent identity with long-lived credentials, meant for a human or a legacy application that needs its own password or access keys. A Group is not something you log in as; it is a bundle you attach policies to once and then add Users into, so twenty engineers who need the same access all inherit it from one place and a single edit updates them all. A Role is the one people misunderstand most – it is an identity with no permanent credentials that is assumed temporarily through STS, handing back short-lived credentials that expire in about an hour.
The reason Roles matter so much in banking is security. At National Bank of India our statement-generator service runs on EC2 and needs to read the customer-statements bucket. If we embedded an access key in that code and it leaked through a log or a repository, an attacker could use it until a human noticed and rotated it. Instead the EC2 instance assumes a Role, receives temporary credentials automatically, and those credentials rotate on their own, so there is simply no long-lived secret to steal.
The way I explain the effective permissions in an interview is this: a User's permissions are the union of its own inline policies plus every policy on every Group it belongs to, and then any explicit deny overrides everything. If nothing allows the action, the implicit deny applies. So in our setup engineers get read access by being in the Developers group, the service gets the same read access through an assumed Role with no keys in the code, and the policy itself is written once and reused everywhere, which is exactly how you keep a bank's access model both secure and maintainable.
Remember one sentence: User is a permanent person, Group bundles them, Role is a temporary badge for machines and cross-account access, and Policy is the JSON rulebook that actually says Allow or Deny.
---