OnCallReady

Lesson 35.8 · AWS I: CLI, IAM, S3 & KMS · 17 min read

IAM: principals, groups, policies and the policy language

In plain words

Think of a big office. People have name badges (IAM users), there are hats anyone allowed can put on for a while to do a specific job, like "fire warden" (roles), and teams are lists of people (groups) so you can give a whole team the same keys at once.

The rules are written on cards (policies): "may open the supply cupboard on floor 2". You can print one card and hand copies to many people (a managed policy), or write a note directly on one person's badge (an inline policy). Nobody can do anything unless some card says so.

IAM: principals, groups and policies

IAM (Identity and Access Management) answers one question for every API call: may this principal perform this action on this resource, right now? This lesson is the vocabulary: who can be a principal, where permissions are written down, and the policy language itself. The next lesson is how AWS combines all of it into a decision.

Need to know: a principal is who makes the request: the root user, an IAM user, a role session, a federated user, or an AWS service. Permissions live in policies - JSON documents with statements of Effect, Action, Resource and an optional Condition. Identity-based policies are attached to users, groups and roles; resource-based policies (bucket policies, KMS key policies, role trust policies) are attached to the resource and name a Principal. Groups collect users but are not principals. Everything is denied unless some policy allows it.

Who can make a request

PrincipalCredentialsUse it for
Root userthe account's email + password (+ MFA)almost nothing: billing, closing the account, a handful of root-only tasks
IAM usera console password and/or up to 2 access keys (long-term)legacy, break-glass, the odd system that cannot use roles
IAM rolenone of its own: whoever assumes it gets temporary credentialsservices, CI, cross-account access, and people (through IAM Identity Center)
Federated useryour IdP's sign-in, turned into a role sessionpeople
AWS servicethe service itself (s3.amazonaws.com, ec2.amazonaws.com)named in resource policies and trust policies

An IAM group is a set of users with policies attached; every member gets them. Groups cannot contain other groups, and a group is not a principal: you cannot name it in a bucket policy or trust policy. Roles are the AWS way to give a workload an identity - the next-but-one lesson is about them.

$ aws iam get-group --group-name platform-admins --query '{Group: Group.GroupName, Users: Users[].UserName}'
{
    "Group": "platform-admins",
    "Users": [
        "learner"
    ]
}
$ aws iam list-attached-group-policies --group-name platform-admins
{
    "AttachedPolicies": [
        {
            "PolicyName": "AdministratorAccess",
            "PolicyArn": "arn:aws:iam::aws:policy/AdministratorAccess"
        }
    ]
}

Where permissions are written

Identity-based policies are attached to a user, group or role and say what it may do. They come in three kinds:

Resource-based policies are attached to a resource and include a Principal: S3 bucket policies, KMS key policies, the trust policy of every role, SNS/SQS/Lambda policies. They are how one account grants access to another.

$ aws iam get-policy --policy-arn arn:aws:iam::aws:policy/ReadOnlyAccess --query 'Policy.[DefaultVersionId,AttachmentCount,UpdateDate]' --output text
v120	1	2026-09-17T15:02:11+00:00
$ aws iam get-policy-version --policy-arn arn:aws:iam::aws:policy/ReadOnlyAccess --version-id v120 --query 'PolicyVersion.Document.Statement[0].Action[:6]'
[
    "iam:Get*",
    "iam:List*",
    "iam:Generate*",
    "s3:Get*",
    "s3:List*",
    "s3:Describe*"
]

get-policy returns the metadata, never the document. The document is a version: get-policy-version with the DefaultVersionId. (AWS's real ReadOnlyAccess lists thousands of actions; the lab's is trimmed to the services in the course.)

The policy language

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "ReadDailyReports",
            "Effect": "Allow",
            "Action": ["s3:GetObject", "s3:ListBucket"],
            "Resource": [
                "arn:aws:s3:::try-reports-111122223333",
                "arn:aws:s3:::try-reports-111122223333/daily/*"
            ],
            "Condition": {
                "IpAddress": {"aws:SourceIp": "198.51.100.0/24"}
            }
        }
    ]
}

Making policies

$ cd ~/oncall-lab/labs/aws
$ aws iam create-group --group-name try-analysts --query Group.Arn --output text
arn:aws:iam::111122223333:group/try-analysts
$ aws iam create-policy --policy-name try-reports-read --policy-document file://try/reports-read.json --query 'Policy.[Arn,DefaultVersionId]' --output text
arn:aws:iam::111122223333:policy/try-reports-read	v1
$ aws iam attach-group-policy --group-name try-analysts --policy-arn arn:aws:iam::111122223333:policy/try-reports-read
$ aws iam create-user --user-name try-ana --query User.UserId --output text
AIDARR2VZGEZYVYPILHVW
$ aws iam add-user-to-group --group-name try-analysts --user-name try-ana
$ aws iam list-groups-for-user --user-name try-ana --query 'Groups[].GroupName' --output text
try-analysts

A new version of a customer managed policy does nothing until it is the default:

$ aws iam create-policy-version --policy-arn arn:aws:iam::111122223333:policy/try-reports-read --policy-document file://try/reports-read-v2.json --query PolicyVersion
{
    "VersionId": "v2",
    "IsDefaultVersion": false,
    "CreateDate": "2026-09-22T20:00:05+00:00"
}
$ aws iam create-policy-version --policy-arn arn:aws:iam::111122223333:policy/try-reports-read --policy-document file://try/reports-read-v2.json --set-as-default --query PolicyVersion.VersionId --output text
v3
$ aws iam list-policy-versions --policy-arn arn:aws:iam::111122223333:policy/try-reports-read --output table
------------------------------------------------------------------
|                       ListPolicyVersions                       |
+----------------------------------------------------------------+
||                           Versions                           ||
|+----------------------------+--------------------+------------+|
||         CreateDate         | IsDefaultVersion   | VersionId  ||
|+----------------------------+--------------------+------------+|
||  2026-09-22T20:00:05+00:00 |  True              |  v3        ||
||  2026-09-22T20:00:05+00:00 |  False             |  v2        ||
||  2026-09-22T20:00:04+00:00 |  False             |  v1        ||
|+----------------------------+--------------------+------------+|
$ aws iam set-default-policy-version --policy-arn arn:aws:iam::111122223333:policy/try-reports-read --version-id v1

That is your rollback button: a bad policy change is undone by making the previous version the default again, in one call. After five versions you must delete one before creating the next (LimitExceeded). An inline policy has none of this:

$ aws iam put-user-policy --user-name try-ana --policy-name allow-own-keys --policy-document file://try/own-keys.json
$ aws iam list-user-policies --user-name try-ana
{
    "PolicyNames": [
        "allow-own-keys"
    ]
}
$ aws iam get-user-policy --user-name try-ana --policy-name allow-own-keys --query 'PolicyDocument.Statement[0].Resource'
"arn:aws:iam::*:user/${aws:username}"
$ aws iam create-policy --policy-name try-broken --policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":"s3:GetObject","Resource":"*","Principal":"*"}]}'
aws: [ERROR]: An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Policy document should not specify a principal.

${aws:username} is a policy variable: the same inline or managed policy lets every user manage only their own keys. And IAM checks the grammar when you save: a Principal in an identity policy is refused at once.

Unique IDs and the AWS-wide prefixes

Every IAM entity has a name, an ARN and a unique ID whose prefix tells you what it is. They show up in CloudTrail, in aws:userid, in key listings:

PrefixWhat
AIDAIAM user
AROAIAM role
AGPAgroup
ANPAmanaged policy
AIPAinstance profile
AKIAlong-term access key ID
ASIAtemporary (STS) access key ID

Delete a user and create one with the same name: same ARN, new unique ID. A bucket policy or trust policy saved while the old user existed does not grant the new one: AWS stored the old unique ID behind the ARN, and once that user is gone the policy shows the bare ID (AIDA...) instead of the ARN - the sign of a stale grant. Save the policy again with the ARN to point it at the new user. It is a safety feature: a new "bob" never inherits the old bob's access.

Designing for least privilege

In an interview: "What is the difference between a managed and an inline policy?" - "A managed policy is a standalone object with an ARN and versions that you attach to many users, groups or roles; AWS managed ones are maintained by AWS. An inline policy is embedded in one principal and deleted with it. Use managed for reuse and rollback, inline for a permission that must never be shared."

You can now: name the kinds of principal and when each is right, tell identity-based from resource-based policies and managed from inline, read and write a policy statement with the right resource ARNs, version a customer managed policy and roll it back, and recognise IAM unique ID prefixes.

Why it helps

Every permission problem in AWS is a question about principals and policies, so the vocabulary of this lesson is what you will use every day: who is the principal, which policies apply, what does the statement say. Being able to read a policy document line by line (Effect, Action, Resource, Condition) is the core skill.

It also matters for design: choosing roles over users, groups over per-user policies, and managed policies with versions over copy-pasted inline ones is the difference between an account you can audit in an afternoon and one nobody dares to touch.

Commands in this lesson

aws cd

FAQ

Is a group a principal?

No. A group is only a way to attach policies to many users at once. You cannot name a group in a resource policy's Principal, and a request is never made "as a group". The principals are the root user, IAM users, roles (through their sessions), federated users and AWS services.

When should I use an IAM user at all?

Rarely. People should sign in through IAM Identity Center and get roles; workloads on AWS should use roles (instance profiles, task roles, service accounts); CI outside AWS should use OIDC to assume a role. IAM users with access keys remain for the few systems that cannot do any of that, and they need key rotation.

What happens when a managed policy changes?

A customer managed policy keeps up to five versions and one of them is the default, the one in effect. Creating a new version with --set-as-default changes the permissions of every user, group and role it is attached to at once. Rolling back is setting an older version as default again.

What does NotAction mean?

A statement with NotAction matches every action except the listed ones. With Effect Allow it grants a lot (PowerUserAccess allows everything except IAM and Organizations this way); with Effect Deny it denies everything except the list. It is not the same as a Deny of those actions: NotAction in an Allow simply leaves them ungranted.

What are the ID prefixes like AIDA and AKIA?

Every IAM entity has a unique ID whose prefix says its type: AIDA a user, AROA a role, AGPA a group, ANPA a managed policy, AKIA a long-term access key, ASIA a temporary one. They show up in CloudTrail and error messages, and the prefix alone tells you whether a leaked key is permanent or will expire.

In an interview Junior

What is the difference between a managed and an inline policy?

A managed policy is a standalone object with its own ARN and up to five versions, one of them the default, that you attach to many users, groups or roles; AWS managed policies such as ReadOnlyAccess are maintained by AWS, customer managed ones by you. An inline policy is embedded in one user, group or role and deleted with it. Use managed policies for reuse, review and rollback, and inline only for a permission that must stay with exactly one principal.

Also asked: What is the difference between an IAM user and an IAM role? · What are the elements of an IAM policy statement? · What is an identity-based policy versus a resource-based policy?

Practise this lesson in the terminal Free, in your browser - a real Ubuntu terminal to try it in, with missions that check your work.