Lab 01 — Cloud IAM Attack-Path Analyzer

Difficulty: 4/5 | Runs locally: yes

Pairs with the Phase 07 WARMUP Chapters 1, 3, 4 (identity is the perimeter, IAM, attack paths as a graph) and the HITCHHIKERS-GUIDE ("Provider Attack-Path Drills"). This is the flagship lab of the phase.

Why This Lab Exists (Purpose & Goal)

In the cloud there is no network perimeter — every resource is an API endpoint, and access is decided by identity and policy. "Identity is the new perimeter" is the defining reframe of cloud security. The goal of this lab is to build the analyzer that thinks about IAM the way an attacker does: not as a flat list of grants, but as a graph of identities, permissions, and trust edges, through which a seemingly harmless principal can reach administrative power by chaining steps.

The Concept, In the Weeds

The fatal mistake reviewers make is checking each role in isolation: "this role is least-privilege, that role is least-privilege, we're fine." But IAM is a graph, and attackers compose edges:

  • Privilege escalation by permissioniam:PassRole + lambda:CreateFunction lets a low-priv identity run code as a high-priv role; iam:CreatePolicyVersion lets you grant yourself anything. Dozens of such escalation primitives exist per cloud.
  • Trust relationships — role A can be assumed by principal B (cross-account AssumeRole, workload-identity federation); a chain of assume-role edges is a path. Third-party trust without an ExternalId is a confused-deputy edge.
  • Resource-based reach — a service account on a compute instance + the metadata service = anyone with code execution on that instance gets the SA's tokens (the SSRF pivot from Phase 02).
  • Wildcardss3:* on * is a blank check that collapses the graph.

The analyzer models all of this and finds the transitive paths to sensitive permissions — producing explainable paths suitable for remediation and regression CI. The bar: no unexplained wildcard privilege and no unintended trust path. The key intellectual move is composition: least privilege per node does not prevent escalation; you must analyze the edges.

Why This Matters for Protecting the Company

A leaked credential or a compromised CI principal is the most common cloud breach entry, and what the attacker can do next is entirely a function of the IAM graph. The difference between "a low-priv credential leaked" and "the cloud account was taken over" is whether an escalation path existed. Analyzing IAM as a graph — and gating changes on "no new path to admin" in CI — is how a company keeps its blast radius small as the IAM estate grows to thousands of roles that no human can reason about manually. This is the single highest-leverage cloud-security control, and it's why tools like this (and the commercial CIEM products) exist.

Build It

Implement the analyzer: model identities, role permissions, and impersonation edges; identify paths to sensitive permissions; catch wildcard grants, direct privilege, transitive impersonation, and cross-tenant trust. Produce explainable paths for remediation and regression.

LAB_MODULE=solution pytest -q
# Export normalized bindings ONLY from your sandbox account — this is an owned-data graph analyzer.

Secure Implementation Patterns

Why the graph, not the node. Each role below is "fine" alone; composed, they reach admin:

ci-deployer  --can--> iam:PassRole + lambda:CreateFunction   (looks low-priv)
   path: create a Lambda whose execution role = AdminRole (PassRole), invoke it -> run AS Admin

The secure pattern — BFS over impersonation edges to a sensitive sink (mirrors solution.py):

def dangerous_paths(roles, bindings, edges, sensitive_permissions, *, max_depth=4):
    role_map = {r.name: r.permissions for r in roles}
    # 1. which subjects have DIRECT reach to a sensitive permission (or a wildcard)?
    direct = {}
    for b in bindings:
        perms = role_map.get(b.role, frozenset())
        if "*" in perms or perms & sensitive_permissions:          # wildcard is a blank check
            direct.setdefault(b.subject, []).append(f"{b.subject} --[{b.role}@{b.resource}]--> sensitive")
    # 2. build the impersonation/assume-role graph
    graph = {}
    for e in edges:
        graph.setdefault(e.source, set()).add(e.target)
    # 3. BFS: from each subject, can we REACH a subject with direct sensitive access?
    results = {}
    for start in sorted(subjects):
        queue, seen = deque([(start, (start,))]), {start}
        while queue:
            cur, path = queue.popleft()
            if cur in direct:                                       # found a transitive escalation path
                results[start] = path + (sorted(direct[cur])[0],); break
            if len(path) > max_depth: continue
            for nxt in sorted(graph.get(cur, ())):
                if nxt not in seen:
                    seen.add(nxt); queue.append((nxt, path + (f"impersonate:{nxt}",)))
    return results                                                  # subject -> explainable path

The output is an explainable path (A -> impersonate:B -> [role@res] -> sensitive) you can hand to remediation and run in CI to fail any change that introduces a new path to admin.

Production practices to carry forward:

  • No wildcards (* actions/resources) and no iam:PassRole to a more-privileged role than the caller — constrain PassRole with conditions.
  • Cut every edge on the path independently (the role grant, the assume-role trust, the resource policy) — defense in depth, since one cut can be re-routed.
  • Third-party trust needs an ExternalId (AWS) / pinned attribute conditions (GCP/Azure) — an unconstrained trust edge is a confused deputy.
  • Protect the metadata endpoint from workloads (the SSRF→credentials pivot) and prefer workload identity over node/instance credentials.
  • Run the analyzer in CI against exported bindings so "no new path to a sensitive permission" becomes a merge gate.

Validation — What You Should Be Able to Do Now

  • Explain why identity, not the network, is the cloud perimeter, and why the control plane is priority one.
  • Find a transitive escalation path (PassRole + CreateFunction; AssumeRole chains) that each role's in-isolation review would miss.
  • Explain why "each role is least-privilege" does not prevent composition into a privileged path.
  • Recognize the metadata-endpoint pivot and the confused-deputy (missing ExternalId) trust edge.

The Broader Perspective

This lab teaches you to think in graphs and reachability about authority — the cloud-scale version of the privilege-boundary review (Phase 05) and the Windows token-escalation analyzer (Phase 05 lab-04). Attackers always think in paths ("from here, what can I reach?"); defenders who think only in isolated grants will always be surprised. The transitive-path mindset is the same one behind network segmentation review (Phase 00/10) and Kubernetes RBAC analysis — enumerate the nodes, model the edges, find the paths to the crown jewels, and cut them. Once you see authority as a graph, you cannot un-see it, and it makes you dramatically more effective at finding the escalation nobody noticed.

Interview Angle

  • "Respond to leaked CI credentials." — Determine blast radius via the IAM graph (what could it reach transitively); revoke/rotate and verify old access fails; audit what it did; close the root cause by moving CI to OIDC federation (no stored secret).
  • "Each role is least-privilege — are we secure?" — Not necessarily; composition (PassRole, AssumeRole chains) can yield a path to admin. Analyze the graph, not the nodes.

Extension (Stretch)

Apply it to AWS, GCP, and Azure binding exports (the provider landing-zone labs 04–06), and run it in CI to fail any change that introduces a new path to a sensitive permission.

References

  • Phase 07 WARMUP Chapters 1, 3, 4; Rhino Security Labs AWS privilege-escalation methods.
  • SPIFFE/SPIRE; GitHub Actions OIDC federation; provider IAM documentation.