When AWS IAM finally made sense to me
A clear mental model for AWS IAM permissions, explicit denies, and Organizations - contrasted with classical RBAC and Kubernetes RBAC.
Автор: Николай ТеневПубликувано: Последна актуализация: 6 мин. четенеArchitecture Notes
I have worked with access-control systems for years, including filesystems, directory-based permission models, and classical RBAC systems such as Kubernetes RBAC. Because of that background, I expected AWS IAM to feel familiar once I went beyond the surface level.
Instead, I found it surprisingly difficult to reason about. I often encountered situations where my intuition said that an action should be allowed, yet AWS denied it, and my usual debugging approach did not lead me to a clear explanation. This post documents the conceptual shift that helped me understand why that intuition was failing and how IAM actually behaves.
The mental model I brought from classical RBAC
Most of my prior experience was with what is usually referred to as classical RBAC, which Kubernetes RBAC largely implements.
In these systems, access is denied by default. Permissions are granted exclusively through allow rules, and the effective permission set for a subject is the union of all allowed actions coming from all roles bound to it. There are no explicit deny rules, and there is no notion of one permission "overriding" another. If an action is allowed anywhere within the relevant scope, it is permitted.
This naturally leads to a particular way of thinking when debugging access issues. When something works, the question is where the permission was granted. When it does not, the answer is simply that no role grants it. There is no concept of a global veto that can invalidate an otherwise valid permission.
This mental model is internally consistent and has served me well in many systems, including Kubernetes.
Why this model does not map well to AWS IAM
AWS IAM appears similar on the surface, but the underlying behavior is fundamentally different.
In IAM, permissions can originate from multiple independent sources: identity-based policies, resource-based policies, service control policies, permission boundaries, and session policies. None of these sources forms a hierarchy in which one level overrides another. There is no ordering such as "user policy beats group policy" or "account policy overrides OU policy".
The mistake I kept making was assuming that such an order existed and trying to reason about which policy should win. IAM does not answer that question because it does not evaluate policies in that way.
How IAM actually evaluates permissions
The model that finally made IAM predictable for me is simple, but very different from classical RBAC.
IAM starts from an implicit deny. It then evaluates all relevant policies together, without precedence between attachment points. Explicit allow statements expand what is potentially permitted. Explicit deny statements remove permissions from that set. If any explicit deny applies to a request, the request is denied, regardless of how many allows exist elsewhere.
This can be summarized as a strict evaluation rule: explicit deny takes precedence over explicit allow, and both take precedence over implicit deny.
An important consequence of this model is that some policy types never grant permissions at all. Service Control Policies and permission boundaries only restrict what can be allowed by other policies. They define upper bounds on permissions rather than sources of access. Even a correctly configured identity or resource policy cannot bypass them.
Once I started thinking in these terms, IAM behavior became far easier to reason about.
A useful contrast: RBAC versus IAM
| Aspect | Classical RBAC (K8s) | AWS IAM |
|---|---|---|
| Default behavior | Deny by default | Deny by default |
| Permission model | Allow-only | Allow + deny |
| Evaluation | Union of allows | Allow minus deny |
| Explicit deny | Not supported | Absolute |
| Debug focus | Where is this allowed? | Where is this denied? |
The difference becomes clearer when stated explicitly.
In classical RBAC systems, including Kubernetes RBAC, access control is deny-by-default and allow-only. Permissions are additive, and there are no explicit denies or global veto mechanisms. Debugging focuses on finding where a permission is granted.
In AWS IAM, access control is also deny-by-default, but both allow and deny rules exist. Policies from multiple layers are merged into a single evaluation, and explicit denies act as absolute vetoes. Debugging therefore focuses on identifying where an action might be denied, not only where it is allowed.
Understanding this distinction explains why RBAC intuition often fails when applied directly to IAM.
Organizations as a control plane rather than a hierarchy
This mental model also clarified how AWS Organizations and SCPs are intended to be used.
Organizations are not a folder structure, and organizational units are not inheritance trees. They are mechanisms for applying constraints at scale. SCPs define the maximum permission surface for accounts within a given scope, but they do not grant permissions and do not create parent-child inheritance in the traditional sense.
Seen as a control plane rather than a hierarchy, the behavior of SCPs becomes much more predictable. Accounts remain strong security boundaries, and OUs are simply places where constraints are attached.
Practical implications
This shift in perspective has practical consequences for both design and debugging.
Instead of asking which policy overrides another, it becomes more productive to ask where an explicit deny could be coming from. Access issues can then be investigated systematically by checking organizational constraints, permission boundaries, resource policies, and identity policies in turn.
More generally, this model encourages deliberate multi-account design, where constraints are applied intentionally rather than discovered through trial and error.
Closing note
I am still early in my AWS SAA preparation, and this post does not represent a complete understanding of IAM. However, this was the first conceptual change that made AWS permissions feel coherent rather than opaque. Once I stopped looking for precedence and started reasoning in terms of merged policies and explicit denies, IAM became much easier to work with.
Summary
- Classical RBAC systems are allow-only and additive.
- AWS IAM merges policies globally and applies explicit denies as absolute vetoes.
- SCPs and permission boundaries restrict permissions but never grant them.
- Treating Organizations as a control plane makes SCP behavior predictable.
Further reading
- AWS IAM policy evaluation logic
https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_evaluation-logic.html - AWS Organizations and SCPs
https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_policies_scps.html - Kubernetes RBAC overview
https://kubernetes.io/docs/reference/access-authn-authz/rbac/