What IAMOps is
Link to section “What IAMOps is”In 30 seconds: the definition
Link to section “In 30 seconds: the definition”IAMOps is an informal contraction of IAM operations (identity and access management operations). This Atlas uses it for authorisation as code: deciding what each person, workload and AI agent may do, writing that down as policy, testing the policy before it ships, and shrinking it as evidence of real use comes in. The principle is old. In 1975 Saltzer and Schroeder wrote: “Every program and every user of the system should operate using the least set of privileges necessary to complete the job.”[1] What changed is scale and tooling: thousands of roles, several policy languages, policy engines and automated reasoning. Beginners can stop after this part.
At a glance
Link to section “At a glance”| Question | Answer |
|---|---|
| Goal | Permissions that are minimal, explainable and testable, for humans, workloads and agents |
| Who | Cloud security and platform engineers; developers building authorisation; security architects; auditors |
| In scope | Least privilege; RBAC, ABAC and ReBAC; how clouds, Kubernetes and policy engines evaluate requests; guardrails written as policy; unused-permission analysis; policy as code; federation in place of long-lived keys |
| Out of scope | Account lifecycle, SSO, MFA (IdentityOps); requests, just-in-time elevation, PAM, recertification (AccessOps); which security guardrails to enforce, posture and drift (CloudSecOps); cluster controls in depth (KubernetesSecOps) |
| Prerequisites | Reading JSON or YAML; basic cloud IAM; IdentityOps helps |
| Outputs | Tested, versioned policies; CI checks; guardrails; permission inventories; exception records |
| Term status | Overlapping synonym for IAM operations; authorisation emphasis is editorial |
The problem it solves
Link to section “The problem it solves”An over-broad grant does nothing visible until someone abuses it. Four documented patterns recur.
- Applications get it wrong. The OWASP Top 10:2025 keeps broken access control at number one; in the data OWASP received, “100% of the applications tested were found to have some form of broken access control”.[7]
- A stolen credential is as dangerous as its permissions. In the Capital One case, the 2021 superseding indictment charged that misconfigured web application firewalls let commands reach servers, which returned “security credentials for particular accounts or roles”; those were used to copy data “for which the accounts had requisite permissions”.[8] A jury convicted the defendant in June 2022.[9] Our reading: the flaw opened the door; the role’s permissions decided how much lay behind it.
- Forgotten non-human permissions become escalation paths. Microsoft reported in January 2024 that Midnight Blizzard compromised
“a legacy test OAuth application that had elevated access to the Microsoft corporate environment”, created malicious OAuth
applications, and used the test application to grant them the Exchange Online
full_access_as_approle, which allows access to mailboxes.[10] - Long-lived keys linger. Datadog’s 2025 study of thousands of organisations found active keys older than a year on 59% of AWS IAM users and 55% of Google Cloud service accounts, and year-old credentials on 40% of Entra ID applications.[11]
What IAMOps is not
Link to section “What IAMOps is not”- Not authentication. SSO, MFA and passkeys belong to IdentityOps.
- Not the access request desk. Ticketed grants, just-in-time elevation, break-glass and recertification belong to AccessOps; IAMOps designs the roles those processes hand out.
- Not cloud posture management. Choosing security guardrails and tracking drift belong to CloudSecOps; account structure and operational guardrails to CloudOps.
- Not data-level security. Row- and column-level policy belongs to DataSecOps.
Where it comes from and where it sits
Link to section “Where it comes from and where it sits”Origins and evolution
Link to section “Origins and evolution”- Principles (1975). Saltzer and Schroeder set out least privilege, fail-safe defaults and complete mediation.[1]
- Roles (1992–2004). Ferraiolo and Kuhn formalised role-based access control in 1992; the NIST model became the standard ANSI/INCITS 359-2004.[12]
- Attributes (2014). NIST SP 800-162 defined attribute-based access control and the decision-point vocabulary.[13]
- Cloud policy languages turned authorisation into JSON documents. In 2018 AWS described Zelkova, which encodes its policy language for an SMT (satisfiability modulo theories) solver and was “invoked many millions of times daily”.[14]
- Relationships and engines (2018–2025). Google’s Zanzibar paper (2019) described relationship-based authorisation at global scale.[15] Open Policy Agent (OPA) became a graduated project of the Cloud Native Computing Foundation (CNCF) in 2021.[16] AWS open-sourced Cedar in 2023;[17] a 2024 paper describes machine-checked proofs of its key properties,[18] and it joined the CNCF sandbox in October 2025.[19]
- A common decision API (2026). AuthZEN Authorization API 1.0 became an OpenID Final Specification in January 2026.[20]
Glossary and mental model
Link to section “Glossary and mental model”| Term | Meaning | Example |
|---|---|---|
| Principal | Who or what acts: user, role, service account, workload, agent | role/orders-deploy |
| ARN | Amazon Resource Name: unique identifier of an AWS resource or principal | arn:aws:iam::111122223333:role/orders-deploy |
| Action, resource | The operation and its target | s3:GetObject on orders-prod/* |
| Context | Facts about the request: time, network, tags, token claims | aws:RequestedRegion |
| Policy | Rules that allow or deny combinations of the above | IAM JSON document, Cedar permit |
| PDP, PEP, PIP, PAP | Policy decision, enforcement, information and administration points: where decisions are computed, applied, fed with attributes, and written and tested[13] | OPA; API gateway; HR directory; Git |
| Grant | A policy that can allow | Identity-based or resource-based policy |
| Ceiling | A policy that can only limit | SCP, RCP, permissions boundary, session policy |
| SCP, RCP | Service and resource control policies: AWS Organizations ceilings, set at the root, an organisational unit (OU) or an account, on what member-account principals may do and what may be done to their resources | Deny leaving the organisation |
| Permissions boundary | A ceiling on one IAM role’s or user’s identity-based permissions | orders-boundary |
| Session policy | A ceiling passed when a role session starts | Read-only support session |
| OIDC federation | A workload exchanges a signed OpenID Connect token for short-lived credentials; a trust policy checks its claims | GitHub job assumes a deploy role |
| RBAC, ABAC, ReBAC | Access by role; by attributes; by relationships between objects | “editor”; same team tag; “owner of the folder” |
| Effective permission | What survives every layer of evaluation | For an identity-based grant: identity allow ∩ boundary ∩ SCP |
| Confused deputy | A less privileged party gets a more privileged one to act for it[21] | Vendor role without an external ID |
Mental model: grants, ceilings and plugs. Grants open holes in stacked filters; ceilings set the largest hole any grant may open; an explicit deny is a plug no grant removes. Surprises come from a forgotten layer: a missing grant, an SCP two levels up, a resource policy granting from the side. Before approving a permission, ask four questions. Who can reach it, including through role assumption and token subjects? What can they do, in words a reviewer can check? Why, and who owns it? Until when, and how will we know it is still used? (The framing is ours.)
How IAMOps relates to other Ops
Link to section “How IAMOps relates to other Ops”Diagram source (Mermaid)
flowchart TB P["PeopleOps"] -->|"HR events"| ID["IdentityOps"] ID -->|"identities"| IAM["IAMOps: what each identity may do"] G["CloudSecOps, CloudOps"] -->|"guardrail choices"| IAM DS["DevSecOps"] -->|"pipeline for checks"| IAM IAM -->|"roles to grant"| AC["AccessOps"] IAM -->|"policies enforced at"| EP["Clusters, gateways, chat"] EP --> KS["KubernetesSecOps"] EP --> API["API Ops"] EP --> CH["ChatOps"] classDef core fill:#e8f3f2,stroke:#16756f,color:#122029 classDef neutral fill:#f1f4f6,stroke:#5b7a87,color:#122029 class IAM core class P,ID,DS,AC,G,EP,KS,API,CH neutral
Text equivalent: HR events flow from PeopleOps to IdentityOps, which hands identities to IAMOps. CloudSecOps (security guardrails) and CloudOps (account structure, operational limits) hand over their choice of guardrails, which IAMOps writes and evaluates as policy; DevSecOps supplies the pipeline that runs policy checks. IAMOps hands roles to AccessOps, which decides when they are granted; its policies are enforced at clusters (KubernetesSecOps), API gateways (API Ops, where IAMOps designs scopes) and chat commands (ChatOps, where it designs the permission model).
Principles and operating model
Link to section “Principles and operating model”A proposed charter and principles
Link to section “A proposed charter and principles”This charter is the Atlas’s proposal; no body publishes one for IAMOps. Mission: every permission in production is intended, owned, minimal, tested and observable. Non-goals: perfect least privilege on day one; replacing the identity provider; removing all standing access (AccessOps).
- Deny by default. Saltzer and Schroeder’s fail-safe default,[1] repeated by OWASP: “Except for public resources, deny by default.”[7]
- Keep grants and ceilings apart. Teams write grants; a smaller group owns ceilings. AWS’s advice for least privilege at scale starts with coarse-grained controls and broad invariants.[22]
- Prefer short-lived, federated credentials. AWS asks workloads to use temporary credentials;[23] Google warns that a service account key “can be even more valuable than a leaked password”.[24]
- Treat policies as code, deployed by pipeline.[25]
- Least privilege is a loop of setting, verifying and refining;[22] NIST SP 800-53 AC-6(7) asks for reviews “to validate the need for such privileges”.[26]
- Make every decision explainable: each allow traces to a statement and an owner; each deny names its layer.
- Know your engine’s failure semantics (how a cloud decides).
Building blocks: grants, ceilings and denies
Link to section “Building blocks: grants, ceilings and denies”| Platform | Grants | Ceilings | Explicit deny | Default |
|---|---|---|---|---|
| AWS | Identity-based and resource-based policies | SCPs, RCPs, permissions boundaries, session policies | Deny statement in any applicable policy[27] |
Implicit deny |
| Google Cloud | Allow policies (role bindings), inherited down the hierarchy | Principal Access Boundary policies | Deny policies[28] | No access |
| Azure | Role assignments, additive[29] | Conditions on role-assignment writes;[30] Azure Policy deny on resource requests[31] |
Deny assignments: Azure-managed; customer-managed in private preview[32] | No access |
| Kubernetes RBAC | Roles and bindings | None inside RBAC | None: “Permissions are purely additive”[33] | No access |
| Cedar | permit policies |
Use forbid policies as guardrails |
forbid overrides permit[34] |
Deny |
Guardrails must use what each platform offers: on Kubernetes they live outside RBAC, in admission control
(KubernetesSecOps); on Azure, Azure Policy and role-assignment conditions do that job. A permission’s name can mislead: on AWS, iam:PassRole lets a principal hand a role, with
all its permissions, to a service;[35] in Kubernetes, list on Secrets returns their contents.[36] Review
effects, not names.
Roles, ownership and RACI
Link to section “Roles, ownership and RACI”AWS warns that a central team writing every policy becomes a bottleneck.[25] This RACI (the Atlas’s proposal) splits the work accordingly. R = responsible, A = accountable, C = consulted, I = informed.
| Activity | Platform or IAM engineering | Application team | Security architecture | Service owner |
|---|---|---|---|---|
| Write organisation guardrails (SCP, RCP, deny, Principal Access Boundary) | R | I | A | I |
| Maintain boundaries, templates and CI checks | A/R | C | C | I |
| Author an application role or app policy | C | R | C (new access only) | A |
Approve new access and high-risk patterns (PassRole, trust changes, iam:*) |
C | C | A/R | I |
| Run the unused-access review | R | R | I | A |
| Triage an AccessDenied incident | R | R | C | A |
| Grant a time-bound exception | C | C (requests) | A/R | C |
Lifecycle of a permission
Link to section “Lifecycle of a permission”| Stage | What happens | Evidence produced |
|---|---|---|
| 1. Need | A task needs access; someone opens a pull request or ticket | Request with purpose and owner |
| 2. Design | Choose model and scope; write the policy and its tests | Policy, tests, design record |
| 3. Verify | CI validates, compares with the previous version, runs tests; security approves new access | Check results, approval |
| 4. Deploy | The pipeline applies the policy; no console edits | Deployment log |
| 5. Observe | Decisions and denials are logged; usage accumulates | Audit logs, last-accessed data |
| 6. Refine | Unused grants are removed or narrowed; exceptions expire | Findings closed with reasons |
| 7. Retire | The workload goes; its roles, keys and trust go with it | Deletion record |
Operating model and governance
Link to section “Operating model and governance”- Central guardrails, delegated authoring: on AWS, application teams write policies inside a permissions boundary that caps
what they can grant (reference implementation).[37] Azure’s counterpart is constrained
delegation (conditions on which roles a delegate may assign, and to whom);[30] Google Cloud’s is the limited IAM admin,
using IAM Conditions on
modifiedGrantsByRole[38] (October 2026). - Risk-tiered review: changes that add no new access merge with normal review; new access, trust changes and guardrail edits need a security code owner’s approval (artefact 4).
Architecture and workflow
Link to section “Architecture and workflow”Reference architecture
Link to section “Reference architecture”Diagram source (Mermaid)
flowchart TB R["Policy repository: policies, tests, templates"] R --> CI["CI: validate, test, compare with main"] CI --> RV["Review: security owner if access grows"] RV --> D["Pipeline deploys policies"] G["Guardrail repository: SCP, RCP, deny"] --> PDP D --> PDP["Decision points: cloud IAM, OPA, Cedar"] PDP -->|"allow or deny"| PEP["Enforcement points: APIs, gateways, apps"] PEP --> L["Decision and audit logs"] L --> U["Usage and unused-access analysis"] U -->|"remove or narrow"| R classDef core fill:#e8f3f2,stroke:#16756f,color:#122029 classDef neutral fill:#f1f4f6,stroke:#5b7a87,color:#122029 classDef decision fill:#fdf3e3,stroke:#c9800f,color:#122029 class R,CI,RV,D core class PDP,PEP decision class G,L,U neutral
Text equivalent: policies, tests and templates live in a repository. CI validates and tests each change and compares it with the main branch; changes that add access also need a security code owner. A pipeline deploys approved policies to decision points (cloud IAM, OPA or Cedar), which also load guardrails from a separate repository. Enforcement points (APIs, gateways, applications) apply each decision and write logs; usage analysis over those logs feeds removals back into the repository.
Decision point: for application data, a library in the service is fast but ships policy with code; a shared service centralises policy but its outage is your outage. Attributes: every attribute a policy reads is a security input, so control who can set it (artefact 2 limits tags).[39]
How a cloud decides
Link to section “How a cloud decides”AWS has the most layers. The figure simplifies the official description[27] to requests inside one account.
Diagram source (Mermaid)
flowchart TB
A["Request: principal, action, resource, context"] --> B("Explicit deny anywhere?")
B -->|"yes"| X1["Deny: explicit"]
B -->|"no"| C("RCPs and SCPs allow?")
C -->|"no"| X2["Deny: guardrail"]
C -->|"yes"| D("Same-account resource policy grants?")
D -->|"to this session or user"| Y["Allow"]
D -->|"none"| E("Identity policy allows?")
D -->|"to the role"| F("Boundary and session policy allow?")
E -->|"no"| X3["Deny: implicit"]
E -->|"yes"| F
F -->|"no"| X4["Deny: ceiling"]
F -->|"yes"| Y
classDef decision fill:#fdf3e3,stroke:#c9800f,color:#122029
classDef risk fill:#fbeceb,stroke:#b4332b,color:#122029
classDef core fill:#e8f3f2,stroke:#16756f,color:#122029
class B,C,D,E,F decision
class X1,X2,X3,X4 risk
class A,Y core
Text equivalent: a request carries a principal, action, resource and context. A matching Deny statement in any applicable policy gives an explicit deny. Otherwise resource control policies (RCPs) and service control policies (SCPs) must allow the request at every level of the organisation. Next comes any same-account resource-based policy: a grant to the requesting role session or IAM user allows the request; a grant to the role skips the identity check, but any permissions boundary and session policy must still allow it. Without such a grant, an identity-based policy must allow the request (otherwise it is an implicit deny), and then any boundary and session policy must allow it too.
Details that bite:
- SCPs and RCPs grant nothing. “No permissions are granted by an SCP”; an allow must exist “at every level from the root through each OU in the direct path to the account”.[40][41] SCPs skip the management account and service-linked roles.[40] RCPs cap what any principal, even an external one, can do to your resources in more than 60 services (October 2026).[42]
- One grant is usually enough. For most resources an allow in the identity-based or the resource-based policy suffices, so review bucket policies as grants. Role trust policies and Key Management Service (KMS) key policies must allow the principal explicitly, and some other services require that too.[27]
- Cross-account requests need two allows: the caller’s identity-based policy in its account, and the resource-based or trust policy in the other.[43]
AllowwithNotActionover-grants. It allows every action not listed, which AWS warns “could result in granting users more permissions than you intended”.[44]- Missing context keys. If a condition key is absent, positive operators fail, but negated ones such as
StringNotLikesucceed.[45] A deny withStringNotEqualstherefore applies to requests that lack the key. - The error names the layer (“with an explicit deny in a service control policy”, “because no identity-based policy allows”), sometimes with the policy’s ARN; some services differ.[46]
Other platforms differ:
- Google Cloud evaluates Principal Access Boundary policies, then deny policies, then allow policies (it notes that “In reality, IAM evaluates all policy types simultaneously”).[28] A deny rule applies if its condition is true “or cannot be evaluated”, so it fails closed. Conditional role bindings “do not override role bindings with no conditions”: an unconditional binding of the same role silently wins.[47]
- Azure checks deny assignments first, then sums role assignments;
Actions - NotActionssubtracts inside one role only, and conditions come last.[29] Conditions can limit role-assignment writes and blob or queue data actions.[30][48] - Kubernetes RBAC only adds; to write a Role with permissions you do not hold you need the
escalateverb, and to bind such a role,bind.[33] - Cedar: any satisfied
forbiddenies, else any satisfiedpermitallows, else deny; a policy that errors is skipped,[34] so an erroring guardrail fails open (Lab 3).
End-to-end workflow: changing a permission
Link to section “End-to-end workflow: changing a permission”Diagram source (Mermaid)
sequenceDiagram participant Dev as Developer participant CI as CI participant Sec as Security owner participant Pipe as Pipeline participant Cloud as Cloud IAM Dev->>CI: Pull request: policy + tests CI->>CI: Validate, test, diff CI-->>Dev: Fail: PassRole on * Dev->>CI: Narrow it, update record CI->>Sec: Pass, review requested Sec->>Pipe: Approve, merge Pipe->>Cloud: Apply policy Cloud-->>Pipe: Applied Note over Dev,Cloud: AccessDenied later? Runbook
Text equivalent: a developer opens a pull request with a policy change and its tests. CI validates, tests and compares it with the main branch, and fails the first attempt: validation flags iam:PassRole on all resources. The developer narrows PassRole to one role path and one service and, because access still grows, updates the permission’s design record. CI passes; the record belongs to the security team, so GitHub requests their review, and a security code owner approves and merges. The pipeline applies the policy to cloud IAM; AccessDenied errors after deployment start the failure runbook.
Happy path: as in the figure once CI passes, with usage later confirming the grant. Exception paths: urgent need goes to time-bound elevation in AccessOps, not a permanent grant; a deploy that breaks callers goes to the worked runbook and a Git rollback.
Practices, standards and tools
Link to section “Practices, standards and tools”Core practices and playbooks
Link to section “Core practices and playbooks”| Practice | Goal | Trigger | Owner | Output | Verification |
|---|---|---|---|---|---|
| Guardrails as policy | Limits no team can lift | New organisation, new risk | Platform with security | SCP, RCP, deny or Principal Access Boundary policy in a protected repo | Tested in a sandbox OU first[40] |
| Delegated authoring | Remove the central bottleneck | A team needs to create roles | Platform | Boundary, templates, delegation policy | Role creation without the boundary fails[37] |
| Policy checks in CI | Stop risky changes before merge | Every policy change | Application team | Validation, comparison, forbidden-action checks; new access routed to security | Required check and code-owner review[49] |
| Federation instead of keys | Remove long-lived secrets | New pipeline or workload | Platform | OIDC trust with narrow subject | No access keys in CI secrets |
| Usage-based refinement | Shrink to what is used | Monthly, and on findings | Service owner | Narrowed policies | Utilisation trend (metrics) |
| Authorisation tests in apps | Correct decisions | Every code change | Application team | Unit and integration tests | OWASP recommends testing authorisation logic[50] |
Choosing a model: RBAC, ABAC and ReBAC compared
Link to section “Choosing a model: RBAC, ABAC and ReBAC compared”| Model | Works well when | Breaks when | Typical implementation |
|---|---|---|---|
| RBAC | Few, stable job functions; coarse infrastructure access; auditors ask “who holds role X?” | Decisions depend on the object (this order, this tenant); roles multiply (“role explosion”)[50] | Cloud roles, Kubernetes RBAC, app roles |
| ABAC | Many resources share governed attributes (team, environment, classification); new resources should not need policy edits[51] | Attributes are not governed: whoever can change a tag can change access | Cloud tag conditions, Azure ABAC conditions, Cedar or Rego conditions |
| ReBAC | Access follows sharing, ownership or hierarchy (documents, folders, organisations)[15] | You cannot keep relationship data fresh and consistent; you need “what can X see?” answers without a search API | Zanzibar-style stores (OpenFGA, SpiceDB), Cedar entity hierarchies |
OWASP says ABAC and ReBAC “should typically be preferred for application development”.[50] Editorial rule of thumb: start with roles; add attributes when roles multiply with resources; add relationships when users share objects.
Methods, standards and frameworks
Link to section “Methods, standards and frameworks”- Binding only where adopted: NIST SP 800-53 Rev. 5 AC-6 (least privilege), with AC-6(7) privilege review, AC-6(9) logging of privileged functions, and AC-3(7) and AC-3(13) for role- and attribute-based enforcement; it binds US federal systems and contracts that adopt it.[26]
- Definitions: NIST SP 800-162 (ABAC; PDP, PEP, PIP, PAP);[13] the NIST RBAC model (ANSI/INCITS 359).[12]
- Recommendations: OWASP for applications;[50][7] AWS’s nine strategies (plan, policy, process).[22][25]
- Interoperability: AuthZEN Authorization API 1.0: subject, action, resource and context in;
{"decision": true}orfalseout; plus search endpoints.[20]
Tooling landscape (as of October 2026)
Link to section “Tooling landscape (as of October 2026)”| Category | Job it does | Verified examples | Watch-outs |
|---|---|---|---|
| Cloud-native analysis | Find external, internal and unused access; validate and compare policies; generate policies from logs | AWS IAM Access Analyzer;[52] Google IAM role recommendations[53] | Observation windows; Region scope for AWS external access; recommendations ignore ACLs and Kubernetes RBAC on Google Cloud |
| General policy engine | Decide in apps, pipelines and clusters from policy plus data | OPA/Rego (CNCF graduated; v1.21.2);[16][54] Cedar (CNCF sandbox; CLI v4.13.0)[19][54] | Data loading, bundle distribution, error semantics |
| Relationship (ReBAC) store | Store relationship tuples and answer checks and lists | OpenFGA (CNCF incubating since 2025-10-28; v1.22.0);[55][54] SpiceDB (v1.56.2)[54] | Keeping relationships in sync with the source of truth; consistency |
| Workload identity | Replace keys with short-lived federated credentials | AWS roles and OIDC federation;[23] Google Workload Identity Federation;[56] Entra workload identity federation;[57] SPIFFE/SPIRE (CNCF graduated 2022)[58] | Subject matching; trust policy sprawl |
| Multi-cloud entitlement analysis (CIEM) | Inventory effective permissions across clouds | Microsoft Defender for Cloud: CIEM for Entra ID, AWS IAM and Google Cloud IAM identities, inside its Defender Cloud Security Posture Management (CSPM) plan[59] | Microsoft retired its standalone Entra Permissions Management on 2025-11-01, keeping permissions discovery and the permissions creep index in Defender CSPM;[60] check what a bundled feature still covers |
Choose an engine by where decisions get their data: relationship-heavy decisions suit a relationship store; attribute decisions that need formal analysis suit Cedar, designed to be “analyzable”;[18] one engine across Kubernetes, Terraform and APIs suggests OPA. Governance matters too: after OPA’s creators joined Apple in August 2025, they wrote on the OPA blog that OPA “remains a CNCF graduated open source project”.[61]
Automation and AI
Link to section “Automation and AI”- Usage-based generation: Access Analyzer drafts policies from CloudTrail activity;[52] AWS suggests generating in a test environment, then adding production ARNs.[25] Google’s recommender uses machine learning to keep permissions a principal is “likely to need in the future”.[53]
- Automated reasoning proves properties instead of sampling examples; AWS describes custom checks as “provable security assurance backed by mathematical proof”.[62]
- AI agents need their own identities and per-task permissions. AuthZEN’s June 2026 working-group drafts for approval workflows (AARP) and Model Context Protocol tool authorisation (COAZ) are not yet standards.[63]
IAMOps in practice
Link to section “IAMOps in practice”Reference implementation
Link to section “Reference implementation”Artefact 1: a guardrail whose exemptions hold. An exemption by role name is only as strong as control over that name: SCPs
bind member-account administrators too,[40] and any of them could create a role called platform-audit-admin and stop the
trail. The companion statement ProtectExemptRoles lets only the platform pipeline create, re-trust, re-permission, tag or delete
the exempt roles. Create those roles while vending the account, before the SCP applies, and let their trust policies admit only the
platform account. ArnNotLike is a negated operator, so it also matches when the key is absent.[45] Attach the SCP next to
FullAWSAccess, first to a sandbox OU.[40]
CloudSecOps chooses the fuller set of telemetry guardrails.
Example status: JSON syntax-checked; not executed (requires an AWS organisation).
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ProtectAuditTrail",
"Effect": "Deny",
"Action": ["cloudtrail:StopLogging", "cloudtrail:DeleteTrail",
"cloudtrail:UpdateTrail", "cloudtrail:PutEventSelectors"],
"Resource": "*",
"Condition": {
"ArnNotLike": {"aws:PrincipalArn": "arn:aws:iam::*:role/platform-audit-admin"}
}
},
{
"Sid": "NoNewAccessKeys",
"Effect": "Deny",
"Action": "iam:CreateAccessKey",
"Resource": "*",
"Condition": {
"ArnNotLike": {"aws:PrincipalArn": "arn:aws:iam::*:role/break-glass-*"}
}
},
{
"Sid": "ProtectExemptRoles",
"Effect": "Deny",
"Action": ["iam:CreateRole", "iam:DeleteRole", "iam:UpdateRole",
"iam:UpdateAssumeRolePolicy", "iam:PutRolePolicy",
"iam:DeleteRolePolicy", "iam:AttachRolePolicy", "iam:DetachRolePolicy",
"iam:PutRolePermissionsBoundary", "iam:DeleteRolePermissionsBoundary",
"iam:TagRole", "iam:UntagRole"],
"Resource": ["arn:aws:iam::*:role/platform-audit-admin",
"arn:aws:iam::*:role/break-glass-*",
"arn:aws:iam::*:role/platform-pipeline"],
"Condition": {
"ArnNotLike": {"aws:PrincipalArn": "arn:aws:iam::*:role/platform-pipeline"}
}
}
]
}
Artefact 2: delegated role creation. This identity policy belongs to the Orders deploy role (Artefact 3), not to a person, so
role changes go through review and the pipeline. Following AWS’s delegation pattern,[37] new roles must carry
orders-boundary, which cannot be edited or removed; tags are limited to approved keys and team=orders, so a role cannot claim
another team’s attributes;[39] and iam:PassRole is limited to the team’s path and one service.[35] The boundary
caps what a new role can do, not who can assume it: iam:CreateRole accepts any trust policy. An identity-perimeter RCP closes
that side. AWS’s data-perimeter examples deny sts:AssumeRole to principals outside the organisation (AWS services and listed
partners excepted) and limit sts:AssumeRoleWithWebIdentity to your tenant of a provider such as GitHub.[64] The sample
also exempts resources tagged dp:exclude:identity, so protect who can set that tag. Access Analyzer’s free external-access
findings show what the exceptions still allow.[52]
Example status: JSON syntax-checked; not executed.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "CreateOrChangeOnlyWithBoundary",
"Effect": "Allow",
"Action": ["iam:CreateRole", "iam:PutRolePermissionsBoundary", "iam:PutRolePolicy",
"iam:AttachRolePolicy", "iam:DetachRolePolicy", "iam:DeleteRolePolicy"],
"Resource": "arn:aws:iam::111122223333:role/orders/*",
"Condition": {
"StringEquals": {
"iam:PermissionsBoundary": "arn:aws:iam::111122223333:policy/orders-boundary"
},
"ForAllValues:StringEquals": {"aws:TagKeys": ["team", "cost-centre"]},
"StringEqualsIfExists": {"aws:RequestTag/team": "orders"}
}
},
{
"Sid": "ManageOwnRoles",
"Effect": "Allow",
"Action": ["iam:GetRole", "iam:ListRolePolicies", "iam:DeleteRole"],
"Resource": "arn:aws:iam::111122223333:role/orders/*"
},
{
"Sid": "TagOwnRolesOnlyAsOrders",
"Effect": "Allow",
"Action": "iam:TagRole",
"Resource": "arn:aws:iam::111122223333:role/orders/*",
"Condition": {
"ForAllValues:StringEquals": {"aws:TagKeys": ["team", "cost-centre"]},
"StringEqualsIfExists": {"aws:RequestTag/team": "orders"}
}
},
{
"Sid": "PassOwnRolesToLambdaOnly",
"Effect": "Allow",
"Action": "iam:PassRole",
"Resource": "arn:aws:iam::111122223333:role/orders/*",
"Condition": {"StringEquals": {"iam:PassedToService": "lambda.amazonaws.com"}}
},
{
"Sid": "NoBoundaryPolicyEdit",
"Effect": "Deny",
"Action": ["iam:CreatePolicyVersion", "iam:DeletePolicy", "iam:DeletePolicyVersion",
"iam:SetDefaultPolicyVersion"],
"Resource": "arn:aws:iam::111122223333:policy/orders-boundary"
},
{
"Sid": "NoBoundaryRemoval",
"Effect": "Deny",
"Action": "iam:DeleteRolePermissionsBoundary",
"Resource": "*"
}
]
}
Artefact 3: a deploy role only one protected environment can assume. For a job that uses an environment, GitHub’s subject is
repo:ORG/REPO:environment:NAME; branch and pull-request subjects apply only without one.[65] Here only acme/orders
jobs that reference production get credentials, so the environment’s settings join the security boundary. Limit its deployment
branches (“No restriction” lets any branch deploy) and disallow the administrator bypass, which is on by default;[66]
DevSecOps covers these settings and plan limits. Repositories created after 15 July 2026, or opted in, use
immutable subjects in which the repository part becomes acme@123456/orders@456789 (owner and repository IDs), so a re-created
namespace cannot reproduce them; match your repository’s format (not on GitHub Enterprise Server).[65]
Example status: JSON syntax-checked; not executed.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated":
"arn:aws:iam::111122223333:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
"token.actions.githubusercontent.com:sub":
"repo:acme/orders:environment:production"
}
}
}
]
}
Artefact 4: policy checks on every pull request. A checker role trusted for repo:acme/orders:pull_request may call only
access-analyzer:ValidatePolicy, CheckNoNewAccess and CheckAccessNotGranted (no resource type, so Resource is
*).[67] Every pull-request job shares that subject, so keep the role harmless; id-token: write “does not give the
workflow permission to modify or write to any resources”.[68]
Validation errors, security warnings and forbidden actions always fail. New access, including a new or renamed file, passes only if
the pull request also changes that policy’s design record in iam/records/ (template); CODEOWNERS gives that folder
to the security team, so GitHub requests their review. A pull_request job runs the pull request’s merge commit, so the workflow
runs the base branch’s copy of the script, CODEOWNERS also covers .github/ and scripts/, and a ruleset requires the check and
code-owner review. There is no paths filter: a required check whose workflow is skipped stays “Pending” and blocks
merging.[69] Not caught: resource-based and trust policies, changes made outside Git, and access that was already too
broad.
Example status: YAML and bash syntax-checked. On 2026-10-17 the script ran in a scratch Git repository with a stub AWS CLI (edit, rename, new file, record present); no real AWS calls were made.
# .github/CODEOWNERS (the last matching pattern wins)
/iam/ @acme/orders-maintainers
/iam/records/ @acme/security
/scripts/ @acme/security
/.github/ @acme/security
name: iam-policy-checks
on: pull_request # no paths filter: a required check must always run
permissions:
id-token: write # can request an OIDC token; grants no repository write access
contents: read
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7 # pin a full commit SHA in production
with:
fetch-depth: 0
- uses: aws-actions/configure-aws-credentials@v6 # pin a SHA here too
with:
role-to-assume: arn:aws:iam::111122223333:role/iam-policy-checker
aws-region: eu-west-1
- name: Run the base branch's checker, not the pull request's
env:
BASE: origin/${{ github.base_ref }}
run: |
git show "$BASE:scripts/check-policies.sh" > "$RUNNER_TEMP/check.sh"
bash "$RUNNER_TEMP/check.sh" "$BASE"
#!/usr/bin/env bash
# check-policies.sh <base-ref>: checks the identity policies in iam/*.json.
# Always fails on validation errors, security warnings and forbidden actions.
# New access (a new, renamed or broader policy) also needs a changed record,
# iam/records/<name>.md, which CODEOWNERS assigns to the security team.
set -euo pipefail
base="$1"
forbidden='actions=iam:CreateAccessKey,iam:DeleteRolePermissionsBoundary'
changed() { git diff --name-only --no-renames --diff-filter=d "$base"...HEAD -- "$@"; }
files="$(changed 'iam/*.json')" # a failing diff fails the job
status=0
old="$(mktemp)"
while IFS= read -r f; do
[ -n "$f" ] || continue
echo "== $f"
issues=$(aws accessanalyzer validate-policy --policy-type IDENTITY_POLICY \
--policy-document "file://$f" --output text --query \
"findings[?findingType=='ERROR' || findingType=='SECURITY_WARNING'].issueCode")
if [ -n "$issues" ]; then echo "validate-policy: $issues"; status=1; fi
result=$(aws accessanalyzer check-access-not-granted --policy-type IDENTITY_POLICY \
--policy-document "file://$f" --access "$forbidden" --query result --output text)
if [ "$result" != "PASS" ]; then echo "forbidden action may be granted"; status=1; fi
new_access=yes # no base version: a new or renamed file
if git cat-file -e "$base:$f" 2>/dev/null; then
git show "$base:$f" > "$old"
result=$(aws accessanalyzer check-no-new-access --policy-type IDENTITY_POLICY \
--existing-policy-document "file://$old" --new-policy-document "file://$f" \
--query result --output text)
if [ "$result" = "PASS" ]; then new_access=no; fi
fi
record="iam/records/$(basename "$f" .json).md"
if [ "$new_access" = yes ] && [ -z "$(changed "$record")" ]; then
echo "new access: update $record for security review"; status=1
fi
done <<< "$files"
rm -f "$old"
exit "$status"
Hands-on labs
Link to section “Hands-on labs”Lab 1: model the AWS evaluation logic (beginner)
Link to section “Lab 1: model the AWS evaluation logic (beginner)”Objective: predict decisions across layers. Prerequisites: Python 3.8 or later; no cloud account or cost. Steps: save
as toy_eval.py, run python3 toy_eval.py, then change one policy at a time, predicting each result first.
Example status: verified by execution (Python 3.8.10; re-run 2026-10-17). A teaching model, not AWS’s implementation: it leaves out RCPs, resource-based policies, cross-account rules and most condition operators.
"""Toy model of AWS policy evaluation inside one account. Teaching only.
Modelled: implicit deny by default; an explicit Deny in any policy wins;
SCPs, identity-based policies, a permissions boundary and a session policy
must each allow the request when they are present.
Not modelled: RCPs, resource-based policies, cross-account rules,
NotAction/NotResource, and condition operators other than three below.
"""
from fnmatch import fnmatchcase
def as_list(value):
return value if isinstance(value, list) else [value]
def matches(pattern, value, ignore_case=False):
if ignore_case:
return fnmatchcase(value.lower(), pattern.lower())
return fnmatchcase(value, pattern)
def condition_holds(operator, key, expected, context):
actual = context.get(key)
options = as_list(expected)
if operator == "StringNotEquals":
return actual is None or actual not in options # missing key: true
if actual is None:
return False # missing key: positive operators do not match
if operator == "StringEquals":
return actual in options
if operator == "StringLike":
return any(matches(o, actual) for o in options)
raise ValueError(f"operator not modelled: {operator}")
def applies(statement, request):
if not any(matches(a, request["action"], ignore_case=True)
for a in as_list(statement["Action"])):
return False
if not any(matches(r, request["resource"])
for r in as_list(statement.get("Resource", "*"))):
return False
for operator, pairs in statement.get("Condition", {}).items():
for key, expected in pairs.items():
if not condition_holds(operator, key, expected, request["context"]):
return False
return True
def effects(policy, request):
found = set()
for statement in policy.get("Statement", []):
if applies(statement, request):
found.add(statement["Effect"])
return found
def evaluate(request, identity, scp_levels=None, boundary=None, session=None):
"""scp_levels: one list of SCPs per level (root, each OU, the account)."""
scp_levels = scp_levels or []
layers = [("a service control policy", p) for level in scp_levels for p in level]
layers += [("an identity-based policy", p) for p in identity]
if boundary is not None:
layers.append(("the permissions boundary", boundary))
if session is not None:
layers.append(("the session policy", session))
for kind, policy in layers: # step 1: any explicit deny wins
if "Deny" in effects(policy, request):
return "DENY", f"explicit deny in {kind}"
for level in scp_levels: # step 2: every level needs at least one allow
if not any("Allow" in effects(p, request) for p in level):
return "DENY", "no service control policy at one level allows it"
if not any("Allow" in effects(p, request) for p in identity):
return "DENY", "no identity-based policy allows it (implicit deny)"
if boundary is not None and "Allow" not in effects(boundary, request):
return "DENY", "the permissions boundary does not allow it"
if session is not None and "Allow" not in effects(session, request):
return "DENY", "the session policy does not allow it"
return "ALLOW", "allowed by every layer that applies"
FULL_ACCESS_SCP = {"Statement": [{"Effect": "Allow", "Action": "*", "Resource": "*"}]}
REGION_SCP = {"Statement": [{
"Effect": "Deny", "Action": "ec2:*", "Resource": "*",
"Condition": {"StringNotEquals": {"aws:RequestedRegion": ["eu-west-1"]}}}]}
APP_ROLE = {"Statement": [
{"Effect": "Allow", "Action": ["s3:GetObject", "s3:PutObject"],
"Resource": "arn:aws:s3:::orders-prod/*"},
{"Effect": "Allow", "Action": ["ec2:Describe*", "ec2:RunInstances", "iam:*"],
"Resource": "*"},
{"Effect": "Deny", "Action": "s3:*", "Resource": "arn:aws:s3:::audit-logs/*"}]}
BOUNDARY = {"Statement": [{"Effect": "Allow", "Action": ["s3:*", "ec2:*"],
"Resource": "*"}]}
CASES = [
("read an order object", "s3:GetObject", "arn:aws:s3:::orders-prod/o-1.json", {}),
("delete the orders bucket", "s3:DeleteBucket", "arn:aws:s3:::orders-prod", {}),
("create an IAM user", "iam:CreateUser", "arn:aws:iam::111122223333:user/x", {}),
("start an instance in us-east-1", "ec2:RunInstances", "*",
{"aws:RequestedRegion": "us-east-1"}),
("start an instance in eu-west-1", "ec2:RunInstances", "*",
{"aws:RequestedRegion": "eu-west-1"}),
("read the audit log", "s3:GetObject", "arn:aws:s3:::audit-logs/2026-10-16.gz", {}),
]
if __name__ == "__main__":
for label, action, resource, context in CASES:
request = {"action": action, "resource": resource, "context": context}
decision, reason = evaluate(request, [APP_ROLE],
scp_levels=[[FULL_ACCESS_SCP],
[FULL_ACCESS_SCP, REGION_SCP]],
boundary=BOUNDARY)
print(f"{decision:5} {label:30} {reason}")
Expected output (copied from the run):
ALLOW read an order object allowed by every layer that applies
DENY delete the orders bucket no identity-based policy allows it (implicit deny)
DENY create an IAM user the permissions boundary does not allow it
DENY start an instance in us-east-1 explicit deny in a service control policy
ALLOW start an instance in eu-west-1 allowed by every layer that applies
DENY read the audit log explicit deny in an identity-based policy
Validation: the boundary neutralises the role’s iam:* grant, the delegation pattern in miniature. Add a session policy
allowing only s3:ListBucket: the order read now fails with “the session policy does not allow it” (also verified).
Troubleshooting: ValueError means an operator the model lacks. Teardown: delete the file.
Lab 2: catch new access before merge with IAM Access Analyzer (intermediate)
Link to section “Lab 2: catch new access before merge with IAM Access Analyzer (intermediate)”Objective: see what the CI script reports. Prerequisites: AWS CLI v2 (commands follow the 2.38.1 reference); credentials
allowed the three access-analyzer check actions; nothing is created. Cost: two custom checks, $0.004 (October 2026);
validation is free.[62]
Example status: bash syntax-checked; not executed. Expected results are taken from the command reference and the policy-check reference, not from a run.
cat > old.json <<'JSON'
{"Version": "2012-10-17", "Statement": [{"Effect": "Allow", "Action": "s3:GetObject",
"Resource": "arn:aws:s3:::orders-prod/*"}]}
JSON
cat > new.json <<'JSON'
{"Version": "2012-10-17", "Statement": [
{"Effect": "Allow", "Action": "s3:GetObject", "Resource": "arn:aws:s3:::orders-prod/*"},
{"Effect": "Allow", "Action": "iam:PassRole", "Resource": "*"}]}
JSON
aws accessanalyzer validate-policy --policy-type IDENTITY_POLICY \
--policy-document file://new.json --query "findings[].[findingType,issueCode]"
aws accessanalyzer check-no-new-access --policy-type IDENTITY_POLICY \
--existing-policy-document file://old.json --new-policy-document file://new.json
aws accessanalyzer check-access-not-granted --policy-type IDENTITY_POLICY \
--policy-document file://new.json --access actions=iam:PassRole
Expected output: a SECURITY_WARNING with issue code PASS_ROLE_WITH_STAR_IN_RESOURCE;[70] both checks return
"result": "FAIL", meaning the policy “might allow” the access.[71] Validation: scope the resource to
arn:aws:iam::111122223333:role/orders/* with an iam:PassedToService condition; the warning should go, while both checks still
fail, correctly, because PassRole is still new access. Troubleshooting: AccessDeniedException means missing access-analyzer permissions;
INVALID_ARN_ACCOUNT means a placeholder where a 12-digit account ID belongs.[70] Teardown: delete both files.
Lab 3: Cedar guardrails and the skip-on-error trap (intermediate)
Link to section “Lab 3: Cedar guardrails and the skip-on-error trap (intermediate)”Objective: watch a forbid guardrail fail open, then let schema validation catch it. Prerequisites: the Cedar CLI (v4.13.0
was current on 2026-10-16; install from the release page or cargo install cedar-policy-cli).[54] Free and local.
Example status: not executed (Cedar CLI not installed here). Command syntax follows the CLI’s sample data; the expected decisions follow Cedar’s documented semantics.[72][34]
schema.cedarschema (Bob’s HR record lacks a clearance, so the attribute is optional):
entity Team;
entity User in [Team] { clearance?: String };
entity Document { owner: User, classification: String };
action view appliesTo { principal: [User], resource: [Document] };
policies.cedar:
@id("finance-can-view")
permit (principal in Team::"finance", action == Action::"view", resource);
@id("restricted-needs-clearance")
forbid (principal, action, resource)
when { resource.classification == "restricted" }
unless { principal.clearance == "high" };
entities.json:
[
{"uid": {"__entity": {"type": "Team", "id": "finance"}}, "attrs": {}, "parents": []},
{"uid": {"__entity": {"type": "User", "id": "alice"}}, "attrs": {"clearance": "high"},
"parents": [{"__entity": {"type": "Team", "id": "finance"}}]},
{"uid": {"__entity": {"type": "User", "id": "bob"}}, "attrs": {},
"parents": [{"__entity": {"type": "Team", "id": "finance"}}]},
{"uid": {"__entity": {"type": "Document", "id": "q3-forecast"}},
"attrs": {"owner": {"__entity": {"type": "User", "id": "alice"}},
"classification": "restricted"},
"parents": []}
]
Steps. (1) cedar authorize --principal 'User::"bob"' --action 'Action::"view"' --resource 'Document::"q3-forecast"' --policies policies.cedar --entities entities.json. (2) cedar validate --policies policies.cedar --schema schema.cedarschema.
(3) Change the guardrail to unless { principal has clearance && principal.clearance == "high" } and repeat both commands.
Expected output. Step 1 prints ALLOW and an evaluation error for restricted-needs-clearance: Bob has no clearance, so the
guardrail errors and is skipped, and finance-can-view lets him read a restricted forecast. Step 2 reports an unsafe access to an
optional attribute.[73] After the fix, validation passes and Bob gets DENY. Validation: Alice still gets ALLOW. Troubleshooting: entity-parsing errors often mean a missing __entity.
Teardown: delete the files. Lesson: fail CI on cedar validate errors; Cedar skips erroring policies so that one bad policy
cannot make an application “suddenly start denying all requests”.[34]
Lab 4: unit-test application authorisation with OPA (advanced)
Link to section “Lab 4: unit-test application authorisation with OPA (advanced)”Objective: encode a tenant-isolation invariant and test it. Prerequisites: OPA v1.x (v1.21.2 was current on 2026-10-16).[54] Free and local.
Example status: not executed (OPA not installed here). Rego v1 syntax; test layout and output format follow OPA’s documentation.[74]
authz.rego:
package orders.authz
default allow := false
same_tenant if input.subject.tenant == input.resource.tenant
allow if {
same_tenant
input.action == "read"
input.subject.role in {"viewer", "editor", "admin"}
}
allow if {
same_tenant
input.action == "refund"
input.subject.role == "admin"
input.resource.amount <= 500
}
authz_test.rego:
package orders.authz_test
import data.orders.authz
order(tenant, amount) := o if {
o := {"tenant": tenant, "amount": amount}
}
test_viewer_reads_own_tenant if {
authz.allow with input as {"subject": {"tenant": "t1", "role": "viewer"},
"action": "read", "resource": order("t1", 10)}
}
test_admin_cannot_cross_tenants if {
not authz.allow with input as {"subject": {"tenant": "t1", "role": "admin"},
"action": "read", "resource": order("t2", 10)}
}
test_refund_limit if {
authz.allow with input as {"subject": {"tenant": "t1", "role": "admin"},
"action": "refund", "resource": order("t1", 500)}
not authz.allow with input as {"subject": {"tenant": "t1", "role": "admin"},
"action": "refund", "resource": order("t1", 501)}
}
Steps: run opa test . -v. Expected output: three PASS lines and PASS: 3/3. Validation: delete same_tenant from
the refund rule; every test still passes, because no test covers a cross-tenant refund. Add that test and watch it fail: each rule
needs its own negative test. Troubleshooting: parse errors on if or in mean OPA older than 1.0, where these keywords
needed explicit imports.[75] Teardown: delete the directory.
Use cases and scenarios
Link to section “Use cases and scenarios”All four are illustrative designs unless a source is cited.
| Scenario | Context | Method | Trade-offs | Success measure |
|---|---|---|---|---|
| CI keys to federation | 40 pipelines hold cloud access keys as CI secrets | One role per pipeline and environment, OIDC trust with exact subjects (artefact 3) | Trust policies multiply; subject formats change | Zero CI access keys; no pipeline role assumable from another repository |
| Multi-tenant SaaS authorisation | Customers share folders with external collaborators | Relationship store for sharing; a tenant-isolation forbid tested on its own |
Relationship data must stay in sync | Every cross-tenant test denies; check latency within budget |
| Right-sizing a legacy role | A shared “app-admin” role used by six services | Split by service, generate drafts from usage, keep a 90-day window plus a calendar of rare jobs | Usage data misses rare paths | Utilisation up; no incident from removals |
| ChatOps command permissions | Engineers run AWS commands from chat | Explicit channel guardrails as ceilings; per-user roles | Convenience against attribution | Every chat action attributable to a person |
CI keys to federation. CircleCI’s January 2023 report says an attacker exfiltrated customers’ “environment variables, keys, and tokens for third-party systems”, and recommends first: “Use OIDC tokens wherever possible to avoid storing long-lived credentials”.[76]
Multi-tenant SaaS. Zanzibar answers relationship checks at huge scale (95th-percentile latency under 10 ms), but its “new enemy” problem shows how stale relationship data can expose new content to someone just removed; use consistency tokens where that matters.[15]
ChatOps. Set channel guardrails explicitly: Amazon Q Developer in chat applications applies the AWS managed
AdministratorAccess policy when an API-created Slack channel configuration names none.[77] The chat path itself, including
bots that act under one shared identity, is ChatOps territory.
Measure, govern, pay
Link to section “Measure, govern, pay”Metrics and KPIs
Link to section “Metrics and KPIs”These metrics are the Atlas’s proposals. Pair every “less access” metric with a speed metric, or least privilege becomes friction.
| KPI | Formula | Data source | Pitfall | Cadence |
|---|---|---|---|---|
| Permission utilisation | Actions used in the window ÷ actions granted, per role; report the median | Last-accessed data, unused-access findings | Action data covers management events only; window shorter than rare jobs[78] | Monthly |
| Wildcard exposure | Statements allowing service:* or * on all resources ÷ all allow statements |
Policy repository scan | Some wildcards are deliberate (read-only on logs) | Monthly |
| Long-lived credentials | Active keys and secrets for workloads; share older than 90 days | Credential reports, key inventories | Counting only one cloud | Weekly |
| Guarded change rate | Policy changes that ran CI checks ÷ all policy changes | CI and deploy logs | Console edits bypass the count | Monthly |
| Post-deploy denials | AccessDenied incidents caused by policy changes per 100 changes | Incident records | Under-reporting of quick fixes | Monthly |
| Time to grant | Median hours from request to deployed permission | Pull request timestamps | Gaming by skipping review | Monthly |
| Exception health | Open exceptions past expiry | Exception register | Silent renewals | Weekly |
Security, privacy and compliance
Link to section “Security, privacy and compliance”- High-risk patterns to gate: actions that write policies or role trust;
iam:PassRoleon*(PASS_ROLE_WITH_STAR_IN_RESOURCE);[70] third-party (vendor) roles without an external ID,[79] and service-principal grants withoutaws:SourceArnoraws:SourceAccount;[21] OIDC trust without a narrowsub; in Kubernetesescalate,bind,impersonate, workload creation and Secret listing.[36] Datadog found 12.2% of third-party integrations in its sample “dangerously overprivileged”.[11] - Audit evidence: repository history, CI results and audit logs are the evidence that AC-6(7) privilege reviews and AC-6(9) logging call for;[26] ComplianceOps packages it.
- Privacy: ABAC attributes and decision logs can be personal data; collect only what policies need and set retention. Rules differ by jurisdiction (PrivacyOps).
Cost, capacity and sustainability
Link to section “Cost, capacity and sustainability”At October 2026 prices, AWS charges nothing for external access analysis, policy validation and generation; unused access analysis is $0.20 per role or user per month, custom checks $0.0020 per call, internal access analysis $9.00 per resource per month.[62] So 850 roles and users cost $170 a month; 400 pull requests with five checks each, $4. Review time costs more; for application engines, so do latency and availability, and caching decisions lets revoked access linger. Energy use is not material.
Assurance of the operating model
Link to section “Assurance of the operating model”Watch denials by layer (spikes after guardrail changes), changes made outside the pipeline, findings archived without reason, expired exceptions and repository drift. Triage these weekly; someone outside the owning team runs the quarterly checklist (our proposal).
When things go wrong
Link to section “When things go wrong”Common failures and troubleshooting
Link to section “Common failures and troubleshooting”| Symptom | Likely cause | Diagnosis | Fix and prevention |
|---|---|---|---|
| AccessDenied right after a guardrail change | New deny, or a level of the path lost its allow | Message says “explicit deny in a service control policy” or “no service control policy allows”[46] | Roll back from Git; stage guardrails through a sandbox OU[40] |
| Pipeline cannot assume its role | Token subject does not match: environment added, repository renamed, immutable format | Compare the token’s sub with the trust policy[65] |
Update the trust condition; test trust policies in CI |
| A role is assumable from too many places | StringLike with * in sub, or no sub at all[80] |
Review trust policies; external-access findings[52] | Exact subjects per environment |
| Clean-up broke a quarterly job | Usage window shorter than the job’s period | Last-accessed data against the job calendar[78] | Restore from Git; window longer than the slowest job; owner sign-off |
Worked runbook: an SCP change blocks deployments
Link to section “Worked runbook: an SCP change blocks deployments”Diagram source (Mermaid)
flowchart TB
S["AccessDenied reported"] --> M{"Which layer is named?"}
M -->|"SCP or RCP"| G["Guardrail: roll back"]
M -->|"identity"| I["Missing grant: pull request"]
M -->|"ceiling"| B["Adjust boundary"]
M -->|"none"| C["Check caller, trust"]
G --> V["Re-run, confirm, add a canary"]
I --> V
B --> V
C --> V
classDef decision fill:#fdf3e3,stroke:#c9800f,color:#122029
classDef risk fill:#fbeceb,stroke:#b4332b,color:#122029
classDef core fill:#e8f3f2,stroke:#16756f,color:#122029
class M decision
class S risk
class G,I,B,C,V core
Text equivalent: read which layer the AccessDenied message names. An SCP or RCP points to a guardrail: roll it back or scope an exception. “No identity-based policy allows” means a missing grant: fix it with a pull request and tests. A permissions boundary or session policy means a ceiling must change. If no layer is named, check the caller, the role’s trust policy and CloudTrail. Every path ends by re-running the action, confirming the fix and adding a canary test.
Illustrative incident. At 10:05 a Region-blocking SCP is attached to the workloads OU; by 10:20 every deployment there
fails: “User: arn:aws:sts::…:assumed-role/orders-deploy/… is not authorized to perform:
iam:GetRole … with an explicit deny in a service control policy”. The cause: the deny allows only eu-west-1 through
StringNotEquals on aws:RequestedRegion, but IAM is a global service with its single endpoint in us-east-1, so every IAM call
the pipeline makes is denied.[81][82]
- Contain. An explicit SCP deny makes this a guardrail incident: freeze guardrail changes and tell the affected teams.
- Find the policy. From the management account (or a delegated administrator), walk from the account up to the root and list
the SCPs at each level. CloudTrail shows Organizations events only in US East (N. Virginia), hence
--region us-east-1.[83]
Example status: bash syntax-checked; not executed. Commands follow the AWS CLI reference; IDs are placeholders.
target=111122223333 # the account; list-parents returns one level at a time
while :; do
echo "== $target"
aws organizations list-policies-for-target --target-id "$target" \
--filter SERVICE_CONTROL_POLICY --query 'Policies[].[Id,Name]' --output text
case "$target" in r-*) break ;; esac # root IDs start with r-
target=$(aws organizations list-parents --child-id "$target" \
--query 'Parents[0].Id' --output text)
done
aws organizations describe-policy --policy-id p-examplepolicy \
--query Policy.Content --output text
aws cloudtrail lookup-events --region us-east-1 --max-results 20 \
--lookup-attributes AttributeKey=EventName,AttributeValue=AttachPolicy
- Decide. Under time pressure, roll back: detach the SCP or restore the previous version from Git, not console exceptions.
- Verify. Re-run one deployment; confirm the error is gone and nothing else changed.
- Prevent. Fix the policy (AWS’s published Region-deny example exempts global services such as IAM, CloudFront and
Organizations with
NotAction),[81] test it in a sandbox OU with the real pipeline, roll out OU by OU,[40] and add a canary deploy to the guardrail pipeline.
Anti-patterns and myths
Link to section “Anti-patterns and myths”- “Grant admin now, tighten later.” Later rarely comes, and usage data will then “prove” that admin is needed.
- Managed policies as the end state. AWS warns they “might not grant least-privilege permissions” for your use case.[84]
- Myth: “an SCP allow grants access.” SCPs never grant; an identity or resource policy must.[40]
- Myth: “a
subcondition makes a role safe.”repo:acme/*still admits every repository in the organisation.[80]
Deciding and adopting
Link to section “Deciding and adopting”When to use and when not to use
Link to section “When to use and when not to use”Invest when several teams create roles across several accounts, CI or workloads need cloud access, applications need per-object decisions, or auditors want evidence. Defer the machinery for one small team in one account: federate humans, avoid keys, start from managed policies. Skip a policy engine for one application with three roles, and relationship infrastructure unless users share objects.
Decision framework
Link to section “Decision framework”Diagram source (Mermaid)
flowchart TB
Q1("Cloud resource?")
Q1 -->|"yes"| Q5("Several teams?")
Q5 -->|"no"| DEF["Defer: managed policies, federation"]
Q5 -->|"yes"| Q2("Every team?")
Q2 -->|"yes"| G["Guardrail policy"]
Q2 -->|"no"| R["Role in a boundary"]
Q1 -->|"no, app data"| Q3("Sharing?")
Q3 -->|"yes"| RB["Relationship store"]
Q3 -->|"no"| Q4("Many roles?")
Q4 -->|"yes"| PE["Policy engine"]
Q4 -->|"no"| CODE["Checks in code"]
classDef decision fill:#fdf3e3,stroke:#c9800f,color:#122029
classDef core fill:#e8f3f2,stroke:#16756f,color:#122029
classDef neutral fill:#f1f4f6,stroke:#5b7a87,color:#122029
class Q1,Q2,Q3,Q4,Q5 decision
class G,R,RB,PE core
class CODE,DEF neutral
Text equivalent: for cloud resources, first ask whether several teams are involved; if not, defer the machinery (managed policies, federated humans, no keys). Otherwise a rule that must hold for every team becomes a guardrail (SCP, RCP, deny or Principal Access Boundary policy); one for a single team becomes a role policy inside a permissions boundary, checked in CI. For application data, sharing or ownership calls for a relationship store or Cedar entity hierarchies; otherwise many roles or services call for a policy engine such as Cedar or OPA with tests, and few roles in one service for checks in code with unit tests.
Maturity model
Link to section “Maturity model”This is the Atlas’s model, not an industry standard.
| Level | Observable behaviour | Exit criterion |
|---|---|---|
| 1 Ad hoc | Console grants, shared keys, admin roles for pipelines | Inventory of roles, keys and owners exists |
| 2 Guarded | Tested guardrails; humans federated | No new system uses long-lived keys |
| 3 Codified | Policies in Git, CI checks, boundaries for delegation | At least 90% of policy changes pass through CI |
| 4 Measured | Utilisation, denials and exceptions reviewed monthly | Targets set for utilisation and time to grant |
| 5 Adaptive | Usage-driven refinement; automated reasoning in review; shared app engine where justified | Removals cause no incidents; audit evidence is generated, not assembled |
Adoption roadmap: 30/60/90 days
Link to section “Adoption roadmap: 30/60/90 days”- Days 1–30: name owners; inventory admin grants, workload keys and cross-account trust; turn on free external-access analysis; write one guardrail and test it in a sandbox OU; federate the most critical pipeline.
- Days 31–60: create the policy repository and CI checks; give one team boundary-based delegation; start unused-access analysis for production accounts; record baseline metrics.
- Days 61–90: federate at least half the remaining pipelines; run the first monthly review; open the exception register; decide whether any application needs a policy engine.
Toolkit
Link to section “Toolkit”Checklists
Link to section “Checklists”Before merging a policy change
- Purpose and owner recorded; validation and forbidden-action checks pass
- New access (including a new or renamed file) has an updated design record and a security code owner’s approval
- Every new allow has a test; one important deny has a test
Before attaching a guardrail
- Tested in a sandbox OU with real pipelines; break-glass and global services exempted deliberately
- Every role-name exemption has a companion deny that protects that name
- Rollback is a Git revert; affected teams told
Trust policy review
-
audand an exactsub(environment or branch) present; immutable subject format checked - Third-party (vendor) roles require an external ID; roles shared between your own accounts name specific principals or
check
aws:PrincipalOrgIDinstead - Service principals limited by
aws:SourceArnoraws:SourceAccount
Quarterly
- Ten random permissions pass the four questions; exceptions past expiry closed; canary denies still deny
Templates
Link to section “Templates”Template status: illustrative, adapt to your tooling.
PERMISSION DESIGN RECORD
Principal ......... role/orders/refund-worker Owner ...... Orders team lead
Purpose ........... Issue refunds up to 500 EUR from the refunds queue
Actions/resources . sqs:ReceiveMessage on refunds-queue; payments:CreateRefund
Conditions ........ aws:SourceAccount = 111122223333; tag env = prod
Ceiling ........... orders-boundary; SCP set "workloads"
Who can reach it .. Lambda function refunds (PassRole limited to lambda)
Tests ............. allow: refund 500 own tenant; deny: refund 501; deny: other tenant
Review by ......... 2027-01-31 (usage window 90 days; quarter-end job noted)
EXCEPTION RECORD
ID / date ......... EXC-0042 / 2026-10-16
Deviation ......... Role ci-legacy keeps an access key (vendor tool lacks OIDC)
Risk .............. Key leakage; mitigated by IP condition and 30-day rotation
Owner / approver .. Build tools lead / Security architect
Expires ........... 2027-01-15 (alert 14 days before); exit plan: vendor upgrade
Is IAMOps a job title? Rarely as one word (a company name and a 2022 vendor term); “IAM operations” usually means running identity services. This page covers its authorisation slice (IdentityOps and AccessOps cover the rest).
Should we start with RBAC or ABAC? Roles first; add attribute conditions once roles multiply and the attributes themselves are governed.
Can we remove unused permissions automatically? Automate the finding and the pull request, not the merge: windows miss rare jobs, and AWS usage data misses PassRole and data-plane calls.[78]
Gateway or service: where should authorisation happen? Both. The gateway enforces coarse rules (authenticated, right scope); only the service knows the object’s owner and tenant, so it decides on the object. See API Ops for the gateway.
How do guardrails coexist with break-glass? Exempt named break-glass roles by aws:PrincipalArn, protect those names with a
companion deny (as in artefact 1), and alert on every use; the break-glass process belongs to
AccessOps.
Going further
Link to section “Going further”Advanced topics and emerging direction
Link to section “Advanced topics and emerging direction”- Proof instead of sampling. Cedar was designed for a “sound and complete logical encoding”.[18] Proofs answer only the question asked: a policy proven to add no new access can still be wrong.
Learning path
Link to section “Learning path”- One day: read your provider’s evaluation logic; run Lab 1.
- One week: Labs 2–4; trust policies for two pipelines; OWASP’s Authorization Cheat Sheet.[50]
- Thirty days: CI checks for one repository; one unused-access review; one guardrail tested in a sandbox; keep a sanitised copy of it as a portfolio piece.
Wrap-up
Link to section “Wrap-up”Executive recap and cheat sheet
Link to section “Executive recap and cheat sheet”- Decide who owns ceilings and who writes grants; delegate with boundaries.
- Remove long-lived workload keys first; federate with exact subjects.
- Check every policy change in CI: validation, new-access comparison, forbidden actions, tests.
- Measure utilisation, wildcard exposure, long-lived credentials and time to grant.
- Refine monthly from usage data, with windows longer than the slowest job.
- Watch out for missing-key semantics, skip-on-error engines,
PassRole, Secretlist, and unconditional bindings.
A judgement call: remove “unused” permissions before year-end?
Link to section “A judgement call: remove “unused” permissions before year-end?”Situation (illustrative): on 1 December the unused-access analyser (90-day window) flags 60 of 75 actions on the
finance-close role. The security lead wants them gone before a 15 December audit; the finance platform owner says the 31 December
year-end close uses some of them, last exercised a year ago.
| Remove all 60 now | Remove in two steps | |
|---|---|---|
| Exposure | Drops at once | 20 actions go now; up to 40 stay for a month under a dated exception |
| Break risk | High: a 90-day window cannot see a yearly job | Low: the close itself shows what is used |
| Audit story | “Removed” | “Removed or scheduled, each with a reason and a date” |
Assumptions: no sign of compromise; one pipeline can reach the role. Decision process: removing everything flagged trusts a window shorter than the job’s cycle. Remove what the owner confirms is never used, keep the rest under an exception that expires in January, and set a longer tracking period (up to 365 days) for annual jobs.[85] Uncertainty: which actions the close needs; last year’s audit logs can answer that if they were kept.
Learning objectives and mastery check
Link to section “Learning objectives and mastery check”You should now be able to explain how grants, ceilings and denies combine; design delegated role creation; write a trust policy for one environment; test policies in CI; evaluate usage-based removals; and set targets that do not reward breakage.
- An identity policy allows
s3:*; the permissions boundary allowss3:GetObject; an SCP allows everything. Can the role delete an object?
Show answer for question 1
No. Effective permissions are the intersection of identity policy, boundary and SCP, so only s3:GetObject survives.[37]
- Your SCP denies
s3:*on the audit bucket whenaws:PrincipalTag/teamisStringNotEqualsaudit. A role that carries noteamtag makes a request. What happens?
Show answer for question 2
- A role trusts GitHub with
StringLikeonsub=repo:acme/*. Is that least privilege?
Show answer for question 3
- In Google Cloud you add a conditional binding of
roles/storage.adminthat expires on Friday, but the user keeps access on Saturday. Why?
Show answer for question 4
An unconditional binding of the same role probably exists; conditional bindings “do not override role bindings with no conditions”.[47]
- A Cedar
forbidreadsprincipal.department, which some users lack. What can go wrong, and how do you prevent it?
Show answer for question 5
- After the quarter in the worked calculation, median utilisation rose from 22% to 41%. The security lead proposes a target of 90% for next quarter. Would you accept it?
Show answer for question 6
Not as stated. Utilisation counts only actions seen in the window, so a high target rewards short windows and removals that break rare jobs (see the judgement call). Use a window longer than the slowest job, a modest target, and pair it with time to grant and post-deploy denials. (Editorial reasoning on the Atlas’s proposed metrics.)
Conclusion
Link to section “Conclusion”“IAMOps” adds no new discipline; this Atlas uses it for the part of IAM operations that decides what identities may do. That part has changed: permissions live in several policy languages with different combining rules, outnumber anyone’s capacity to review by hand, and are increasingly held by workloads and agents. The answer is a working loop, not a perfect policy: central ceilings, grants written by the teams that understand them, machine checks on every change, and regular removal of what usage shows is unneeded. Pick one role this week, ask the four questions, and make the answers code.
Sources and further reading
Link to section “Sources and further reading”Each footnote gives the date it was checked; other dates are the sources’ own. Product documentation changes often: re-check it
before relying on it; job postings and company profiles are ephemeral. Claim-level support: research/iamops-ledger.md.
Changelog. 2026-10-16: first edition (Lab 1 executed; other examples syntax-checked, not executed). 2026-10-17, r1: revised after review reviews/iamops-r1.md (response
reviews/iamops-r1-response.md): evaluation figure, SCP exemptions, delegated trust and the CI gate corrected; cross-account,
NotAction, Azure and Google delegation added; neighbour duplication cut. 2026-10-17, r2: revised after the second independent
review (reviews/iamops-r1.md, response reviews/iamops-r1-response.md): term status now covers Axiom’s 2022 “IAMOps” use;
mastery question 2 re-based on a key that can be absent; Kubernetes escalate/bind, CIEM and RCP tag clauses corrected.
2026-10-17, post-score polish: Google Cloud footnotes moved to docs.cloud.google.com; expired posting marked; CIEM row
sourced; Figure 6 gains a “defer” leaf; assurance gains a cadence; repeats cut.
The Protection of Information in Computer Systems — J. H. Saltzer, M. D. Schroeder, Proceedings of the IEEE, 1975. Checked 2026-10-16. ↩︎ ↩︎ ↩︎
IAMOPS company profile — Clutch. Checked 2026-10-17. (The company’s own site, iamops.io, refused automated access.) ↩︎
Axiom raises $7 million Seed round for IAMOps platform — CTech by Calcalist, 2022-12-06. Checked 2026-10-17. ↩︎
How IAMOps will make IAM scalable, Axiom emerges with $7M in funding — T. Keary, VentureBeat, 2022-12-06. Checked 2026-10-17. ↩︎
Manager Team Identity Engineering & Operations — Endeavour Group job posting (expired by 2026-10-17). Checked 2026-10-17. ↩︎
Head of IAM Operations — Ework Group job posting (marked expired by 2026-10-17). Checked 2026-10-16. ↩︎
A01:2025 Broken Access Control — OWASP Top 10:2025. Checked 2026-10-16. ↩︎ ↩︎ ↩︎
Superseding indictment, United States v. Paige A. Thompson — US Department of Justice, filed 2021-06-17. Checked 2026-10-16. ↩︎
Former Amazon Employee Found Guilty in 2019 Capital One Data Breach — R. Lakshmanan, The Hacker News, 2022-06-21. Checked 2026-10-17. ↩︎
Midnight Blizzard: Guidance for responders on nation-state attack — Microsoft Threat Intelligence, 2024-01-25. Checked 2026-10-17. ↩︎
State of Cloud Security — Datadog, October 2025. Checked 2026-10-17. ↩︎ ↩︎
Role Based Access Control project — NIST CSRC. Checked 2026-10-16. ↩︎ ↩︎
Guide to Attribute Based Access Control (ABAC), SP 800-162 — NIST, 2014 (updated 2019). Checked 2026-10-16. ↩︎ ↩︎ ↩︎
Semantic-based automated reasoning for AWS access policies using SMT — Backes et al., FMCAD 2018. Checked 2026-10-16. ↩︎
Zanzibar: Google’s Consistent, Global Authorization System — Pang et al., USENIX ATC 2019. Checked 2026-10-16. ↩︎ ↩︎ ↩︎
Open Policy Agent (OPA) — CNCF. Checked 2026-10-16. ↩︎ ↩︎
Introducing Cedar, an open-source language for access control — AWS What’s New, 2023-05-10. Checked 2026-10-17. ↩︎
Cedar: A New Language for Expressive, Fast, Safe, and Analyzable Authorization — Cutler et al., arXiv, 2024. Checked 2026-10-16. ↩︎ ↩︎ ↩︎
Authorization API 1.0 — OpenID AuthZEN Working Group, Final, 2026-01-11. Checked 2026-10-16. ↩︎ ↩︎
The confused deputy problem — AWS IAM User Guide. Checked 2026-10-17. ↩︎ ↩︎
Strategies for achieving least privilege at scale, Part 1 — J. Du Lac, E. Enekwizu, AWS Security Blog, 2024-07-09. Checked 2026-10-16. ↩︎ ↩︎ ↩︎
Security best practices in IAM — AWS IAM User Guide. Checked 2026-10-16. ↩︎ ↩︎
Best practices for managing service account keys — Google Cloud, updated 2026-10-13. Checked 2026-10-17. ↩︎
Strategies for achieving least privilege at scale, Part 2 — J. Du Lac, E. Enekwizu, AWS Security Blog, 2024-07-09. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎
SP 800-53 Rev. 5 catalog, OSCAL release 5.2.0 — NIST, updated 2026-05-11. Checked 2026-10-16. ↩︎ ↩︎ ↩︎
How AWS enforcement code logic evaluates requests — AWS IAM User Guide. Checked 2026-10-17. ↩︎ ↩︎ ↩︎
IAM policy types — Google Cloud, updated 2026-10-13. Checked 2026-10-17. ↩︎ ↩︎
What is Azure role-based access control? — Microsoft Learn, 2024-03-12. Checked 2026-10-16. ↩︎ ↩︎
Delegate Azure access management to others — Microsoft Learn, 2026-03-30. Checked 2026-10-17. ↩︎ ↩︎ ↩︎
Azure Policy definitions deny effect — Microsoft Learn, 2025-03-04. Checked 2026-10-17. ↩︎
List Azure deny assignments — Microsoft Learn, 2026-10-06. Checked 2026-10-16. ↩︎
Using RBAC Authorization — Kubernetes documentation. Checked 2026-10-16. ↩︎ ↩︎
Authorization — Cedar documentation. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
Grant a user permissions to pass a role to an AWS service — AWS IAM User Guide. Checked 2026-10-16. ↩︎ ↩︎
Role Based Access Control Good Practices — Kubernetes documentation. Checked 2026-10-17. ↩︎ ↩︎
Permissions boundaries for IAM entities — AWS IAM User Guide. Checked 2026-10-17. ↩︎ ↩︎ ↩︎ ↩︎
Set limits on granting roles — Google Cloud, updated 2026-10-13. Checked 2026-10-17. ↩︎
Controlling access to AWS resources using tags — AWS IAM User Guide. Checked 2026-10-17. ↩︎ ↩︎
Service control policies (SCPs) — AWS Organizations User Guide. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
SCP evaluation — AWS Organizations User Guide. Checked 2026-10-16. ↩︎
Resource control policies (RCPs) — AWS Organizations User Guide. Checked 2026-10-17. ↩︎
Cross-account policy evaluation logic — AWS IAM User Guide. Checked 2026-10-17. ↩︎
IAM JSON policy elements: NotAction — AWS IAM User Guide. Checked 2026-10-17. ↩︎
Condition operators — AWS IAM User Guide. Checked 2026-10-16. ↩︎ ↩︎ ↩︎
Troubleshoot access denied error messages — AWS IAM User Guide. Checked 2026-10-16. ↩︎ ↩︎
Overview of IAM Conditions — Google Cloud, updated 2026-10-13. Checked 2026-10-17. ↩︎ ↩︎
What is Azure attribute-based access control? — Microsoft Learn, 2025-05-19. Checked 2026-10-17. ↩︎
Custom policy checks — AWS IAM User Guide. Checked 2026-10-16. ↩︎
Authorization Cheat Sheet — OWASP. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
Define permissions based on attributes with ABAC — AWS IAM User Guide. Checked 2026-10-16. ↩︎
Using AWS IAM Access Analyzer — AWS IAM User Guide. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎
Overview of role recommendations — Google Cloud, updated 2026-10-13. Checked 2026-10-17. ↩︎ ↩︎
Release pages for OPA, Cedar, OpenFGA and SpiceDB — GitHub. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
Workload Identity Federation — Google Cloud, updated 2026-10-13. Checked 2026-10-17. ↩︎
Workload identity federation — Microsoft Learn, 2025-04-09. Checked 2026-10-16. ↩︎
Cloud infrastructure entitlement management (CIEM) — Microsoft Learn, 2025-07-15. Checked 2026-10-17. ↩︎
Microsoft Entra Permissions Management end of sale and retirement — Microsoft Tech Community, 2025-04-01, updated 2025-09-29. Checked 2026-10-17. ↩︎
A note from Teemu, Tim, and Torin to the OPA community — OPA Blog, 2025-08-20. Checked 2026-10-17. ↩︎
AWS IAM Access Analyzer pricing — AWS. Checked 2026-10-16. ↩︎ ↩︎ ↩︎
Authorization for the agent era: new AuthZEN Working Group Drafts — OpenID Foundation, 2026-06-15. Checked 2026-10-16. ↩︎
Resource control policy examples for data perimeters (
identity_perimeter_rcp.jsonand its README) — AWS Samples on GitHub. Checked 2026-10-17. ↩︎OpenID Connect reference — GitHub Docs. Checked 2026-10-17. ↩︎ ↩︎ ↩︎ ↩︎
Deployments and environments — GitHub Docs. Checked 2026-10-17. ↩︎
Actions, resources, and condition keys for IAM Access Analyzer — AWS Service Authorization Reference. Checked 2026-10-16. ↩︎
Configuring OpenID Connect in Amazon Web Services — GitHub Docs. Checked 2026-10-16. ↩︎
About code owners, Events that trigger workflows and Workflow syntax — GitHub Docs. Checked 2026-10-17. ↩︎
IAM Access Analyzer policy check reference — AWS IAM User Guide. Checked 2026-10-17. ↩︎ ↩︎ ↩︎
check-no-new-access and check-access-not-granted — AWS CLI 2.38.1 Command Reference. Checked 2026-10-16. ↩︎
Cedar CLI and sample data — cedar-policy on GitHub. Checked 2026-10-16. ↩︎
Policy validation — Cedar documentation. Checked 2026-10-16. ↩︎ ↩︎
Policy Testing — OPA documentation. Checked 2026-10-16. ↩︎
v0 Compatibility — OPA documentation. Checked 2026-10-16. ↩︎
CircleCI Jan 4, 2023 security incident report — R. Zuber, CircleCI, 2023-01-12. Checked 2026-10-16. ↩︎
CreateSlackChannelConfiguration — AWS API Reference. Checked 2026-10-16. ↩︎
Refine permissions using last accessed information — AWS IAM User Guide. Checked 2026-10-16. ↩︎ ↩︎ ↩︎
Access to AWS accounts owned by third parties — AWS IAM User Guide. Checked 2026-10-17. ↩︎
Avoiding mistakes with AWS OIDC integration conditions — S. Piper, Wiz Blog, 2025-01-01. Checked 2026-10-16. ↩︎ ↩︎ ↩︎
Denies access to AWS based on the requested Region — AWS IAM User Guide. Checked 2026-10-16. ↩︎ ↩︎
AWS global condition context keys — AWS IAM User Guide. Checked 2026-10-17. ↩︎ ↩︎ ↩︎
Logging API calls with AWS CloudTrail for AWS Organizations — AWS Organizations User Guide. Checked 2026-10-17. ↩︎
Prepare for least-privilege permissions — AWS IAM User Guide. Checked 2026-10-16. ↩︎
Create an unused access analyzer — AWS IAM User Guide. Checked 2026-10-16. ↩︎