The Ops Atlas xOps School

Security & Compliance Overlapping synonym Sources verified

IAMOps

IAMOps is an informal label for IAM operations. This Atlas uses it for authorisation as code: designing, testing and evaluating the policies that decide what people, workloads and agents may do, so permissions stay minimal, explainable and testable.

Key takeaways

  • IAMOps is not a defined discipline. The one-word label names a DevOps services company and was one identity vendor's 2022 term for automated access operations; "IAM operations" is a job family for running identity services. This page's focus on authorisation policy is an editorial choice shared with IdentityOps and AccessOps.
  • Every decision has four inputs (principal, action, resource, context) and a combining rule. AWS, Google Cloud, Azure, Kubernetes and Cedar combine allows and denies differently; learn the rule before you write the policy.
  • Separate grants from ceilings. Identity and resource policies grant; SCPs, RCPs, permissions boundaries and session policies only limit; an explicit deny wins wherever it applies.
  • Least privilege is a loop: set permissions, verify them in continuous integration (CI) with policy checks and tests, then refine them from usage data, whose blind spots (quarterly jobs, PassRole, data-plane calls) you must know.
  • In our judgement, long-lived keys are among the cheapest large risks to remove: federate CI and workloads, and narrow each token's subject to one repository and a protected environment.
  • Error semantics are security semantics: Google applies a deny rule whose condition cannot be evaluated, while Cedar skips any policy that errors, including a forbid. Validate policies against a schema.

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.

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

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_app role, 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]
  • 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.
  • 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]
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.)

Figure 1. Where IAMOps sits among its neighbours: what each one hands over
HR eventsidentitiesguardrail choicespipeline for checksroles to grantpolicies enforced atPeopleOpsIdentityOpsIAMOps: what each identitymay doCloudSecOps, CloudOpsDevSecOpsAccessOpsClusters, gateways, chatKubernetesSecOpsAPI OpsChatOps
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).

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).

  1. Deny by default. Saltzer and Schroeder’s fail-safe default,[1] repeated by OWASP: “Except for public resources, deny by default.”[7]
  2. 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]
  3. 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]
  4. Treat policies as code, deployed by pipeline.[25]
  5. 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]
  6. Make every decision explainable: each allow traces to a statement and an owner; each deny names its layer.
  7. 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.

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
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
  • 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).
Figure 2. Policy as code: from authoring to runtime decision and back
allow or denyremove or narrowPolicy repository: policies,tests, templatesCI: validate, test, comparewith mainReview: security owner ifaccess growsPipeline deploys policiesGuardrail repository: SCP,RCP, denyDecision points: cloud IAM,OPA, CedarEnforcement points: APIs,gateways, appsDecision and audit logsUsage and unused-accessanalysis
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]

AWS has the most layers. The figure simplifies the official description[27] to requests inside one account.

Figure 3. How AWS evaluates a request inside one account (simplified)
yesnonoyesto this session or usernoneto the rolenoyesnoyesRequest: principal, action,resource, contextExplicit deny anywhere?Deny: explicitRCPs and SCPs allow?Deny: guardrailSame-account resourcepolicy grants?AllowIdentity policy allows?Boundary and session policyallow?Deny: implicitDeny: ceiling
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]
  • Allow with NotAction over-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 StringNotLike succeed.[45] A deny with StringNotEquals therefore 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 - NotActions subtracts 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 escalate verb, and to bind such a role, bind.[33]
  • Cedar: any satisfied forbid denies, else any satisfied permit allows, 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”
Figure 4. A permission change from pull request to production
Cloud IAMPipelineSecurity ownerCIDeveloperAccessDenied later? RunbookPull request: policy + testsValidate, test, diffFail: PassRole on *Narrow it, update recordPass, review requestedApprove, mergeApply policyApplied
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.

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.

  • 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} or false out; plus search endpoints.[20]
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]

  • 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]

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"

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.

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.

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
  • High-risk patterns to gate: actions that write policies or role trust; iam:PassRole on * (PASS_ROLE_WITH_STAR_IN_RESOURCE);[70] third-party (vendor) roles without an external ID,[79] and service-principal grants without aws:SourceArn or aws:SourceAccount;[21] OIDC trust without a narrow sub; in Kubernetes escalate, 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).

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.

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).

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”
Figure 5. Triage path for an AccessDenied report
SCP or RCPidentityceilingnoneAccessDenied reportedWhich layer is named?Guardrail: roll backMissing grant: pull requestAdjust boundaryCheck caller, trustRe-run, confirm, add a canary
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]

  1. Contain. An explicit SCP deny makes this a guardrail incident: freeze guardrail changes and tell the affected teams.
  2. 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
  1. Decide. Under time pressure, roll back: detach the SCP or restore the previous version from Git, not console exceptions.
  2. Verify. Re-run one deployment; confirm the error is gone and nothing else changed.
  3. 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.
  • “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 sub condition makes a role safe.” repo:acme/* still admits every repository in the organisation.[80]

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.

Figure 6. Where should an authorisation rule live?
yesnoyesyesnono, app datayesnoyesnoCloud resource?Several teams?Defer: managed policies,federationEvery team?Guardrail policyRole in a boundarySharing?Relationship storeMany roles?Policy engineChecks in code
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.

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
  • 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.

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

  • aud and an exact sub (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:PrincipalOrgID instead
  • Service principals limited by aws:SourceArn or aws:SourceAccount

Quarterly

  • Ten random permissions pass the four questions; exceptions past expiry closed; canary denies still deny

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.

  • 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.
  • 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.
  • 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, Secret list, 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.

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.

  1. An identity policy allows s3:*; the permissions boundary allows s3: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]

  1. Your SCP denies s3:* on the audit bucket when aws:PrincipalTag/team is StringNotEquals audit. A role that carries no team tag makes a request. What happens?
Show answer for question 2

The deny applies. aws:PrincipalTag/team enters the request context only when the principal carries that tag,[82] and negated operators evaluate to true when the key is missing.[45] (aws:RequestedRegion could not set this trap: AWS says it “is always included in the request context”.[82])

  1. A role trusts GitHub with StringLike on sub = repo:acme/*. Is that least privilege?
Show answer for question 3

No. Any workflow in any repository of the organisation, on any branch, can assume it.[80] Use exact subjects per repository and environment, and protect the environment.[65]

  1. In Google Cloud you add a conditional binding of roles/storage.admin that 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]

  1. A Cedar forbid reads principal.department, which some users lack. What can go wrong, and how do you prevent it?
Show answer for question 5

For users without the attribute, the forbid errors and is skipped, so a matching permit allows access.[34] Guard with has, and fail CI on schema-validation errors.[73]

  1. 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.)

“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.

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.


  1. The Protection of Information in Computer Systems — J. H. Saltzer, M. D. Schroeder, Proceedings of the IEEE, 1975. Checked 2026-10-16. ↩︎ ↩︎ ↩︎

  2. IAMOPS company profile — Clutch. Checked 2026-10-17. (The company’s own site, iamops.io, refused automated access.) ↩︎

  3. Axiom raises $7 million Seed round for IAMOps platform — CTech by Calcalist, 2022-12-06. Checked 2026-10-17. ↩︎

  4. How IAMOps will make IAM scalable, Axiom emerges with $7M in funding — T. Keary, VentureBeat, 2022-12-06. Checked 2026-10-17. ↩︎

  5. Manager Team Identity Engineering & Operations — Endeavour Group job posting (expired by 2026-10-17). Checked 2026-10-17. ↩︎

  6. Head of IAM Operations — Ework Group job posting (marked expired by 2026-10-17). Checked 2026-10-16. ↩︎

  7. A01:2025 Broken Access Control — OWASP Top 10:2025. Checked 2026-10-16. ↩︎ ↩︎ ↩︎

  8. Superseding indictment, United States v. Paige A. Thompson — US Department of Justice, filed 2021-06-17. Checked 2026-10-16. ↩︎

  9. Former Amazon Employee Found Guilty in 2019 Capital One Data Breach — R. Lakshmanan, The Hacker News, 2022-06-21. Checked 2026-10-17. ↩︎

  10. Midnight Blizzard: Guidance for responders on nation-state attack — Microsoft Threat Intelligence, 2024-01-25. Checked 2026-10-17. ↩︎

  11. State of Cloud Security — Datadog, October 2025. Checked 2026-10-17. ↩︎ ↩︎

  12. Role Based Access Control project — NIST CSRC. Checked 2026-10-16. ↩︎ ↩︎

  13. Guide to Attribute Based Access Control (ABAC), SP 800-162 — NIST, 2014 (updated 2019). Checked 2026-10-16. ↩︎ ↩︎ ↩︎

  14. Semantic-based automated reasoning for AWS access policies using SMT — Backes et al., FMCAD 2018. Checked 2026-10-16. ↩︎

  15. Zanzibar: Google’s Consistent, Global Authorization System — Pang et al., USENIX ATC 2019. Checked 2026-10-16. ↩︎ ↩︎ ↩︎

  16. Open Policy Agent (OPA) — CNCF. Checked 2026-10-16. ↩︎ ↩︎

  17. Introducing Cedar, an open-source language for access control — AWS What’s New, 2023-05-10. Checked 2026-10-17. ↩︎

  18. Cedar: A New Language for Expressive, Fast, Safe, and Analyzable Authorization — Cutler et al., arXiv, 2024. Checked 2026-10-16. ↩︎ ↩︎ ↩︎

  19. Cedar — CNCF. Checked 2026-10-16. ↩︎ ↩︎

  20. Authorization API 1.0 — OpenID AuthZEN Working Group, Final, 2026-01-11. Checked 2026-10-16. ↩︎ ↩︎

  21. The confused deputy problem — AWS IAM User Guide. Checked 2026-10-17. ↩︎ ↩︎

  22. Strategies for achieving least privilege at scale, Part 1 — J. Du Lac, E. Enekwizu, AWS Security Blog, 2024-07-09. Checked 2026-10-16. ↩︎ ↩︎ ↩︎

  23. Security best practices in IAM — AWS IAM User Guide. Checked 2026-10-16. ↩︎ ↩︎

  24. Best practices for managing service account keys — Google Cloud, updated 2026-10-13. Checked 2026-10-17. ↩︎

  25. Strategies for achieving least privilege at scale, Part 2 — J. Du Lac, E. Enekwizu, AWS Security Blog, 2024-07-09. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎

  26. SP 800-53 Rev. 5 catalog, OSCAL release 5.2.0 — NIST, updated 2026-05-11. Checked 2026-10-16. ↩︎ ↩︎ ↩︎

  27. How AWS enforcement code logic evaluates requests — AWS IAM User Guide. Checked 2026-10-17. ↩︎ ↩︎ ↩︎

  28. IAM policy types — Google Cloud, updated 2026-10-13. Checked 2026-10-17. ↩︎ ↩︎

  29. What is Azure role-based access control? — Microsoft Learn, 2024-03-12. Checked 2026-10-16. ↩︎ ↩︎

  30. Delegate Azure access management to others — Microsoft Learn, 2026-03-30. Checked 2026-10-17. ↩︎ ↩︎ ↩︎

  31. Azure Policy definitions deny effect — Microsoft Learn, 2025-03-04. Checked 2026-10-17. ↩︎

  32. List Azure deny assignments — Microsoft Learn, 2026-10-06. Checked 2026-10-16. ↩︎

  33. Using RBAC Authorization — Kubernetes documentation. Checked 2026-10-16. ↩︎ ↩︎

  34. Authorization — Cedar documentation. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  35. Grant a user permissions to pass a role to an AWS service — AWS IAM User Guide. Checked 2026-10-16. ↩︎ ↩︎

  36. Role Based Access Control Good Practices — Kubernetes documentation. Checked 2026-10-17. ↩︎ ↩︎

  37. Permissions boundaries for IAM entities — AWS IAM User Guide. Checked 2026-10-17. ↩︎ ↩︎ ↩︎ ↩︎

  38. Set limits on granting roles — Google Cloud, updated 2026-10-13. Checked 2026-10-17. ↩︎

  39. Controlling access to AWS resources using tags — AWS IAM User Guide. Checked 2026-10-17. ↩︎ ↩︎

  40. Service control policies (SCPs) — AWS Organizations User Guide. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  41. SCP evaluation — AWS Organizations User Guide. Checked 2026-10-16. ↩︎

  42. Resource control policies (RCPs) — AWS Organizations User Guide. Checked 2026-10-17. ↩︎

  43. Cross-account policy evaluation logic — AWS IAM User Guide. Checked 2026-10-17. ↩︎

  44. IAM JSON policy elements: NotAction — AWS IAM User Guide. Checked 2026-10-17. ↩︎

  45. Condition operators — AWS IAM User Guide. Checked 2026-10-16. ↩︎ ↩︎ ↩︎

  46. Troubleshoot access denied error messages — AWS IAM User Guide. Checked 2026-10-16. ↩︎ ↩︎

  47. Overview of IAM Conditions — Google Cloud, updated 2026-10-13. Checked 2026-10-17. ↩︎ ↩︎

  48. What is Azure attribute-based access control? — Microsoft Learn, 2025-05-19. Checked 2026-10-17. ↩︎

  49. Custom policy checks — AWS IAM User Guide. Checked 2026-10-16. ↩︎

  50. Authorization Cheat Sheet — OWASP. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  51. Define permissions based on attributes with ABAC — AWS IAM User Guide. Checked 2026-10-16. ↩︎

  52. Using AWS IAM Access Analyzer — AWS IAM User Guide. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎

  53. Overview of role recommendations — Google Cloud, updated 2026-10-13. Checked 2026-10-17. ↩︎ ↩︎

  54. Release pages for OPA, Cedar, OpenFGA and SpiceDB — GitHub. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  55. OpenFGA — CNCF. Checked 2026-10-16. ↩︎

  56. Workload Identity Federation — Google Cloud, updated 2026-10-13. Checked 2026-10-17. ↩︎

  57. Workload identity federation — Microsoft Learn, 2025-04-09. Checked 2026-10-16. ↩︎

  58. SPIFFE — CNCF. Checked 2026-10-16. ↩︎

  59. Cloud infrastructure entitlement management (CIEM) — Microsoft Learn, 2025-07-15. Checked 2026-10-17. ↩︎

  60. Microsoft Entra Permissions Management end of sale and retirement — Microsoft Tech Community, 2025-04-01, updated 2025-09-29. Checked 2026-10-17. ↩︎

  61. A note from Teemu, Tim, and Torin to the OPA community — OPA Blog, 2025-08-20. Checked 2026-10-17. ↩︎

  62. AWS IAM Access Analyzer pricing — AWS. Checked 2026-10-16. ↩︎ ↩︎ ↩︎

  63. Authorization for the agent era: new AuthZEN Working Group Drafts — OpenID Foundation, 2026-06-15. Checked 2026-10-16. ↩︎

  64. Resource control policy examples for data perimeters (identity_perimeter_rcp.json and its README) — AWS Samples on GitHub. Checked 2026-10-17. ↩︎

  65. OpenID Connect reference — GitHub Docs. Checked 2026-10-17. ↩︎ ↩︎ ↩︎ ↩︎

  66. Deployments and environments — GitHub Docs. Checked 2026-10-17. ↩︎

  67. Actions, resources, and condition keys for IAM Access Analyzer — AWS Service Authorization Reference. Checked 2026-10-16. ↩︎

  68. Configuring OpenID Connect in Amazon Web Services — GitHub Docs. Checked 2026-10-16. ↩︎

  69. About code owners, Events that trigger workflows and Workflow syntax — GitHub Docs. Checked 2026-10-17. ↩︎

  70. IAM Access Analyzer policy check reference — AWS IAM User Guide. Checked 2026-10-17. ↩︎ ↩︎ ↩︎

  71. check-no-new-access and check-access-not-granted — AWS CLI 2.38.1 Command Reference. Checked 2026-10-16. ↩︎

  72. Cedar CLI and sample data — cedar-policy on GitHub. Checked 2026-10-16. ↩︎

  73. Policy validation — Cedar documentation. Checked 2026-10-16. ↩︎ ↩︎

  74. Policy Testing — OPA documentation. Checked 2026-10-16. ↩︎

  75. v0 Compatibility — OPA documentation. Checked 2026-10-16. ↩︎

  76. CircleCI Jan 4, 2023 security incident report — R. Zuber, CircleCI, 2023-01-12. Checked 2026-10-16. ↩︎

  77. CreateSlackChannelConfiguration — AWS API Reference. Checked 2026-10-16. ↩︎

  78. Refine permissions using last accessed information — AWS IAM User Guide. Checked 2026-10-16. ↩︎ ↩︎ ↩︎

  79. Access to AWS accounts owned by third parties — AWS IAM User Guide. Checked 2026-10-17. ↩︎

  80. Avoiding mistakes with AWS OIDC integration conditions — S. Piper, Wiz Blog, 2025-01-01. Checked 2026-10-16. ↩︎ ↩︎ ↩︎

  81. Denies access to AWS based on the requested Region — AWS IAM User Guide. Checked 2026-10-16. ↩︎ ↩︎

  82. AWS global condition context keys — AWS IAM User Guide. Checked 2026-10-17. ↩︎ ↩︎ ↩︎

  83. Logging API calls with AWS CloudTrail for AWS Organizations — AWS Organizations User Guide. Checked 2026-10-17. ↩︎

  84. Prepare for least-privilege permissions — AWS IAM User Guide. Checked 2026-10-16. ↩︎

  85. Create an unused access analyzer — AWS IAM User Guide. Checked 2026-10-16. ↩︎

Related Ops

IdentityOps

IdentityOps is the day-to-day operation of workforce and machine identities: HR-driven joiner–mover–leaver, directories and identity providers, SSO, SCIM provisioning, phishing-resistant MFA and recovery, and the lifecycle of non-human identities.

AccessOps

AccessOps is not a standard term. Here it means running the moment of access beyond birthright, above all privileged access: how people request, receive, use and give back access, with no standing admin rights, expiry the system enforces, a tested break-glass path and reviews that can say no.

CloudSecOps

CloudSecOps is running security for a cloud estate at the provider-API layer: guardrails that block unsafe configuration, continuous posture checks, complete audit logs, and detection and response for control-plane activity. The label is emerging and loosely defined.

KubernetesSecOps

KubernetesSecOps is a rare name for Kubernetes security operations: running the controls that decide who may change a cluster, what may run in it, what may talk to what, and how an attack in progress is noticed and contained.

DevSecOps

DevSecOps is the practice of building security into the software delivery pipeline and into the teams that run it, so that every change is checked, traceable and safe to ship without a separate security phase that stalls delivery.

API Ops

API lifecycle operations: design-first with OpenAPI, API gateways, versioning and deprecation, rate limiting and quotas, API security (OAuth 2.0, keys), contract testi…

In preparation

ChatOps

ChatOps is the practice of running operational work through shared chat: bots post the events people need and run allow-listed commands on request, so actions, results and the discussion around them are visible, permissioned and auditable.

CloudOps

CloudOps (cloud operations) is running workloads on public cloud through the provider's control plane: account hierarchy, guardrails, quotas, Regions, provider signals and managed-service change. The label is common, but no neutral body defines it.

Practise and get certified

From xOps School

Practise IAMOps and get certified

The free IAMOps track on xOps School has study notes, a 25-question practice quiz with explanations, and a verifiable certificate when you score 70% or more. Sign in with Google to start.

For teams

Instructor-led training

Live batches for your team, with completion certificates and reports for L&D/HR.

Source: master.md (the editorial source of this page) · built 2026-10-17 · diagrams rendered with @mermaid-js/mermaid-cli 11.17.0 (mermaid 11.17.2)