Identity and Access Management
Controlling who can do what in the cloud: identities, roles and policies, least privilege, MFA, and federation with your organization's accounts.
Why identity is the new perimeter
In a traditional data center, the firewall around the building was the main line of defense: whoever was outside could not touch the servers. In the cloud, the management interface is on the internet. Every action — starting a server, reading a file from storage, deleting a database — is an application programming interface (API) call to the provider, and anyone with valid credentials can make it from anywhere. A leaked access key is therefore as serious as an open door to the data center.
Identity and access management (IAM) is the set of services and rules that decide who can make which call on which resource. It is often described as "the new perimeter": network controls such as security groups still matter, but most serious cloud incidents involve an identity with too many permissions or credentials that ended up in the wrong hands.
Authentication versus authorization
Two separate questions are answered for every request:
- Authentication (AuthN): who are you? The caller proves its identity with a password and a second factor, a signed token, or cryptographically signed API requests.
- Authorization (AuthZ): what may you do? The platform checks the authenticated identity's policies against the requested action and resource.
A user who can sign in but gets "access denied" has passed authentication and failed authorization. Keeping the two apart helps when troubleshooting: a wrong password is a different problem from a missing permission.
Principals: who can act
A principal (Azure says security principal) is anything that can be authenticated and granted permissions. Identities that can act are users, groups and workload identities; a role is how permissions are packaged and handed to them. (In AWS a group cannot sign in or act on its own: it only collects users so they can share permissions.)
| Identity | What it is | AWS | Azure | Google Cloud |
|---|---|---|---|---|
| User | One person | IAM user, or a user in IAM Identity Center | Microsoft Entra ID user | Google account or Cloud Identity user |
| Group | A set of users that share permissions | IAM group | Entra ID group | Google group |
| Workload identity | An identity for an application, not a person | IAM role for a service (for example an instance profile) | Managed identity, service principal | Service account |
Roles are not in this table on purpose. In Azure and Google Cloud a role is only a set of permissions, granted to one of these identities. In AWS an IAM role is a separate identity without a password or permanent keys: a principal assumes it and then acts with temporary credentials. The next sections explain both.
Users and groups
Give permissions to groups, not to individual users. When someone joins the finance team, adding them to the finance-readers group gives them exactly what colleagues have; when they move on, removing them takes it all away. Permissions scattered over individual users are impossible to review.
Roles
The word "role" means slightly different things per provider. In AWS an IAM role is an identity with permissions but without a password or permanent keys: a user, an application or a service assumes the role and receives temporary credentials for it. A trust policy on the role says who may assume it. In Azure and Google Cloud a role is a named collection of permissions (such as Reader or Storage Object Viewer) that you grant to a principal at a certain scope. In both cases the idea is the same: permissions belong to a job, and identities take on that job.
Service accounts and managed identities
Applications need permissions too: a web application that reads uploads from storage, a nightly job that writes reports. A service account is an identity for such a workload rather than a person. The best kind has no secret that anyone has to store: the platform hands the running code short-lived credentials automatically.
- AWS: attach an IAM role to an Amazon Elastic Compute Cloud (EC2) instance (through an instance profile), a Lambda function or a container task; the software development kit (SDK) picks up the temporary credentials by itself.
- Azure: a managed identity (system-assigned, tied to one resource, or user-assigned and shareable) gets role assignments like any other principal.
- Google Cloud: attach a service account to the virtual machine or service; avoid downloading service account keys.
Pipelines outside the cloud, such as a GitHub Actions workflow, can use workload identity federation: the pipeline presents a signed token from its own platform and exchanges it for short-lived cloud credentials, so no cloud secret is stored in the pipeline at all. See DevOps and CI/CD.
Policies: what is allowed
An IAM policy is a document that lists permissions: which actions are allowed or denied on which resources, sometimes under conditions. Policies are attached to principals (identity-based policies) or to resources such as a storage bucket (resource-based policies).
An AWS policy, line by line
AWS writes policies in JavaScript Object Notation (JSON). This identity-based policy lets an accounting application read invoices stored in Amazon Simple Storage Service (S3) but never delete them:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ReadInvoices",
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:ListBucket"],
"Resource": ["arn:aws:s3:::example-invoices", "arn:aws:s3:::example-invoices/*"]
},
{
"Sid": "NeverDeleteInvoices",
"Effect": "Deny",
"Action": ["s3:DeleteObject", "s3:DeleteObjectVersion"],
"Resource": "arn:aws:s3:::example-invoices/*"
}
]
}
-
"Version": "2012-10-17"is the version of the policy language, not a date you should change. Always use this value. -
"Statement"is a list of rules; each one is judged separately. -
"Sid"(statement ID) is an optional label that makes the rule easy to recognize in reviews and logs. -
"Effect"is"Allow"or"Deny". -
"Action"names API operations asservice:Operation:s3:GetObjectreads a file (an object) ands3:ListBucketlists the bucket's contents. Wildcards are possible (s3:Get*), but every wildcard widens the permission. -
"Resource"names the resources by Amazon Resource Name (ARN).arn:aws:s3:::example-invoicesis the bucket itself (needed for listing);arn:aws:s3:::example-invoices/*is every object in it (needed for reading). -
The second statement denies deleting objects and their older
versions (in a versioned bucket,
s3:DeleteObjectVersionremoves a version for good). Even if another policy ever grantss3:*, this deny still wins, as the next section shows. A policy can still be changed by an administrator, though: for records that must truly survive, use Object Lock (see Cloud Storage).
There is no "Principal" element: an identity-based
policy applies to whichever user, group or role it is attached to.
Resource-based policies, such as a bucket policy, do name a
principal. Policies can also carry a "Condition", for
example to require encrypted connections or a specific source
network.
Azure role assignments and scope
Azure role-based access control (Azure RBAC) takes a different shape. Instead of writing policy documents per identity, you create a role assignment with three parts:
- a security principal: a user, group, service principal or managed identity;
- a role definition: a built-in role such as Owner, Contributor or Reader, or a custom role;
- a scope: a management group, a subscription, a resource group or a single resource.
Permissions are inherited downward: Reader on a subscription means Reader on every resource group and resource in it. So the scope is where least privilege happens — assign roles at the smallest scope that works. Azure RBAC is additive: a principal's permissions are the sum of its role assignments. Deny assignments exist, but you cannot create them directly; Azure features such as deployment stacks and managed applications create them.
$ az role assignment list --assignee ana@example.com --all --output table
Principal Role Scope --------------- ------ ----------------------------------------------------------------------------------- ana@example.com Reader /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-invoices
Google Cloud combines both ideas: an allow policy on a resource (organization, folder, project or single resource) binds principals to roles, inherited downward like Azure's scopes, and separate deny policies can block specific permissions.
How a request is evaluated
When a principal has several policies — from its groups, from the resource, from organization-wide guardrails — the platform needs a rule to combine them. AWS uses this order, and the same idea appears wherever deny rules exist:
- Explicit deny: if any applicable statement denies the request, it is denied. Nothing overrides it.
- Explicit allow: otherwise, if any applicable statement allows it, it is allowed.
- Implicit deny: otherwise it is denied. Everything starts forbidden.
The evaluator below applies these three rules to the policy above.
It then adds a second, far too generous policy (s3:* on
every resource) to show what an explicit deny protects and what it
does not:
import json
from fnmatch import fnmatchcase
POLICY = json.loads("""
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ReadInvoices",
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:ListBucket"],
"Resource": ["arn:aws:s3:::example-invoices", "arn:aws:s3:::example-invoices/*"]
},
{
"Sid": "NeverDeleteInvoices",
"Effect": "Deny",
"Action": ["s3:DeleteObject", "s3:DeleteObjectVersion"],
"Resource": "arn:aws:s3:::example-invoices/*"
}
]
}
""")
def as_list(value):
return value if isinstance(value, list) else [value]
def matches(statement, action, resource):
return (any(fnmatchcase(action, a) for a in as_list(statement["Action"]))
and any(fnmatchcase(resource, r) for r in as_list(statement["Resource"])))
def evaluate(policies, action, resource):
matching = [s for p in policies for s in p["Statement"] if matches(s, action, resource)]
for s in matching:
if s["Effect"] == "Deny":
return f"DENY (explicit deny in {s['Sid']})"
for s in matching:
if s["Effect"] == "Allow":
return f"ALLOW (allowed by {s['Sid']})"
return "DENY (implicit: nothing allows it)"
# A second policy on the same user that is far too generous.
TOO_BROAD = {"Statement": [{"Sid": "FullS3", "Effect": "Allow", "Action": "s3:*", "Resource": "*"}]}
requests = [
("s3:GetObject", "arn:aws:s3:::example-invoices/2026/10/inv-1042.pdf"),
("s3:PutObject", "arn:aws:s3:::example-invoices/2026/10/inv-1043.pdf"),
("s3:DeleteObject", "arn:aws:s3:::example-invoices/2026/10/inv-1042.pdf"),
("s3:GetObject", "arn:aws:s3:::example-payroll/2026/10.csv"),
]
for label, policies in (("invoice policy only", [POLICY]), ("plus s3:* on *", [POLICY, TOO_BROAD])):
print(f"-- {label}")
for action, resource in requests:
print(f"{action:16} {resource.split(':::')[1]:38} {evaluate(policies, action, resource)}")
-- invoice policy only s3:GetObject example-invoices/2026/10/inv-1042.pdf ALLOW (allowed by ReadInvoices) s3:PutObject example-invoices/2026/10/inv-1043.pdf DENY (implicit: nothing allows it) s3:DeleteObject example-invoices/2026/10/inv-1042.pdf DENY (explicit deny in NeverDeleteInvoices) s3:GetObject example-payroll/2026/10.csv DENY (implicit: nothing allows it) -- plus s3:* on * s3:GetObject example-invoices/2026/10/inv-1042.pdf ALLOW (allowed by ReadInvoices) s3:PutObject example-invoices/2026/10/inv-1043.pdf ALLOW (allowed by FullS3) s3:DeleteObject example-invoices/2026/10/inv-1042.pdf DENY (explicit deny in NeverDeleteInvoices) s3:GetObject example-payroll/2026/10.csv ALLOW (allowed by FullS3)
With the broad policy added, the application can suddenly overwrite invoices and read payroll files. Only the delete is still blocked, because it has an explicit deny. That is the lesson: a deny protects one specific thing, but it cannot make up for allows that are too wide.
This is a simplification. Real evaluation also checks conditions, treats action names case-insensitively, and involves more policy types — for example organization-wide guardrails (AWS service control policies; in Azure, Azure Policy restricts resource settings rather than identities) and permission boundaries, which can only limit what other policies allow, never add to it.
Least privilege in practice
Least privilege means every identity gets the permissions its job needs and nothing more. The idea is old; in the cloud it matters more because one over-privileged key can reach everything, from any network. In practice:
- Start from nothing and add permissions, instead of starting from administrator and taking away.
- Prefer the smallest scope: one bucket, one resource group, one project — not the whole account or subscription.
- Use built-in, job-shaped roles (Reader, a database operator role) before broad ones such as Owner, Contributor or Editor.
- Separate environments: developers may have wide rights in a development account but read-only rights in production.
- Remove permissions that are not used; providers show when a permission or key was last used.
The root account and global administrators
Every cloud account has one identity that is more powerful than any policy. In AWS it is the root user, the email address that created the account; in Azure it is the Global Administrator role in Microsoft Entra ID; in Google Cloud the super administrator of the organization's Cloud Identity or Workspace. This root account can change billing, close the account and grant itself anything. Treat it like the key to the building's safe:
- Do not use it for daily work. Create administrator identities for that, and use the root account only for the few tasks that require it.
- Protect it with strong multi-factor authentication, preferably a hardware security key. Providers have started to require MFA for these accounts.
- Never create access keys for the AWS root user.
- Keep the credentials in a safe place known to more than one trusted person, and alert on every sign-in. For Entra ID, Microsoft recommends two emergency access ("break-glass") accounts that are excluded from normal Conditional Access policies but protected with phishing-resistant MFA such as a passkey or FIDO2 key.
Multi-factor authentication
Multi-factor authentication (MFA) requires a second proof besides the password: an authenticator app, a hardware key or a passkey. A stolen password alone is then not enough. Require MFA for every person who signs in to a cloud console, not only administrators; phishing-resistant methods such as hardware keys and passkeys are stronger than codes that can be typed into a fake login page.
Temporary credentials instead of access keys
Scripts and tools authenticate with access keys: an ID and a secret that sign each API request. A long-lived key that never expires is a liability. It ends up in a configuration file, a laptop backup or — very often — a public code repository, where automated scanners find it within minutes.
The better pattern is temporary credentials: a user or application authenticates once and assumes a role, and the platform hands out credentials that expire after minutes or hours. In AWS the Security Token Service (STS) issues these; Azure and Google Cloud issue short-lived tokens in the same way. A leaked temporary credential is useful to an attacker only until it expires.
A good first command with any cloud command-line interface (CLI) is to ask "who am I?". With the AWS CLI, a user who signed in through a federated identity provider and assumed a role sees something like this:
$ aws sts get-caller-identity
{
"UserId": "AROA3XFRBF23EXAMPLE:ana@example.com",
"Account": "111122223333",
"Arn": "arn:aws:sts::111122223333:assumed-role/InvoiceReader/ana@example.com"
}
The assumed-role in the ARN shows that Ana is working
with a role session, not with a permanent key.
Federation and single sign-on
Most organizations already have a central directory of staff accounts. Creating a second set of users in each cloud account means separate passwords, and forgotten accounts when people leave. Identity federation solves this: the cloud platform trusts an external identity provider (IdP), which authenticates the person and sends a signed statement of who they are and which groups they belong to. The cloud then maps those groups to roles.
Single sign-on (SSO) is what users experience: they sign in once, with MFA, to the organization's identity provider and then open the cloud console, email and other applications without signing in again. The most common identity providers and cloud-side services are:
- Microsoft Entra ID (formerly Azure Active Directory), which is also the identity service of Azure and Microsoft 365 itself;
- AWS IAM Identity Center (formerly AWS Single Sign-On), which connects an external IdP to many AWS accounts and assigns permission sets;
- Google Cloud Identity and workforce identity federation for Google Cloud; and independent providers such as Okta.
Under the hood, federation uses standard protocols: Security Assertion Markup Language (SAML) 2.0, and OpenID Connect (OIDC), which is built on OAuth 2.0. The main security benefit is simple: when someone leaves and their account is disabled in the directory, they can no longer sign in; existing sessions end when their tokens expire, so keep session lengths short.
Access reviews and logging
Permissions grow over time: a temporary grant for a project stays, people change teams and keep their old groups. Two habits keep this under control.
- Access reviews: at fixed intervals, owners confirm who still needs which access, and the rest is removed. Tools help: Microsoft Entra access reviews, AWS IAM Access Analyzer (which also reports unused access and resources shared outside the account), and Google Cloud's role recommendations based on actual use.
- Logging: API calls — who, what, when, from where, and whether it was allowed — are recorded in an audit log such as AWS CloudTrail, the Azure activity log and Entra sign-in logs, or Google Cloud Audit Logs. Management calls are recorded by default; data access, such as reading objects, must usually be enabled separately (CloudTrail data events, Azure diagnostic logs, Google Data Access audit logs) and costs extra. These logs answer "who deleted this?" and reveal suspicious sign-ins. Store them in a separate, protected account and alert on sensitive events, such as root sign-ins. See Monitoring, Logging and Observability.