<?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>IAM Roles Archives - Linuxcent</title>
	<atom:link href="https://linuxcent.com/tag/iam-roles/feed/" rel="self" type="application/rss+xml" />
	<link>https://linuxcent.com/tag/iam-roles/</link>
	<description>Infrastructure security, from the kernel up.</description>
	<lastBuildDate>Sat, 09 May 2026 18:38:06 +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>IAM Roles Archives - Linuxcent</title>
	<link>https://linuxcent.com/tag/iam-roles/</link>
	<width>32</width>
	<height>32</height>
</image> 
<site xmlns="com-wordpress:feed-additions:1">211632295</site>	<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>IAM Roles vs Policies: How Cloud Authorization Actually Works</title>
		<link>https://linuxcent.com/iam-roles-policies-permissions-explained/</link>
					<comments>https://linuxcent.com/iam-roles-policies-permissions-explained/#respond</comments>
		
		<dc:creator><![CDATA[Vamshi Krishna Santhapuri]]></dc:creator>
		<pubDate>Tue, 14 Apr 2026 05:05:54 +0000</pubDate>
				<category><![CDATA[Cloud IAM]]></category>
		<category><![CDATA[ABAC]]></category>
		<category><![CDATA[AWS IAM Policy]]></category>
		<category><![CDATA[Cloud Security]]></category>
		<category><![CDATA[IAM]]></category>
		<category><![CDATA[IAM Policies]]></category>
		<category><![CDATA[IAM Roles]]></category>
		<category><![CDATA[Least Privilege]]></category>
		<category><![CDATA[RBAC]]></category>
		<guid isPermaLink="false">https://linuxcent.com/iam-roles-policies-permissions-explained/</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"> 12</span> <span class="rt-label rt-postfix">minutes</span></span>A clear breakdown of IAM roles, policies, and permissions — the building blocks of cloud access control in AWS, GCP, and Azure with RBAC and ABAC patterns.</p>
<p>The post <a href="https://linuxcent.com/iam-roles-policies-permissions-explained/">IAM Roles vs Policies: How Cloud Authorization Actually Works</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"> 12</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> → <strong>IAM Roles vs Policies</strong> → <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>Every cloud permission is atomic: one action (<code class="" data-line="">s3:GetObject</code>) on one resource class — the indivisible unit of access</li>
<li>Policies group permissions into documents with conditions; roles carry policies and are assigned to identities</li>
<li>Never attach policies directly to users — roles are the indirection layer that makes access auditable and revocable</li>
<li>AWS roles have <strong>two</strong> required configs: trust policy (who can assume) + permission policy (what they can do) — both must be right</li>
<li>GCP binds roles to resources; AWS attaches policies to identities — the mental models run in <strong>opposite directions</strong></li>
<li><code class="" data-line="">iam:PassRole</code> in AWS and <code class="" data-line="">iam.serviceAccounts.actAs</code> in GCP are privilege escalation vectors — always scope to specific ARNs, never <code class="" data-line="">*</code></li>
</ul>
<hr />
<h2 id="the-big-picture">The Big Picture</h2>
<p>Three primitives underlie every cloud IAM system. Learn how they connect and any cloud access model becomes readable.</p>
<pre><code class="" data-line="">  THE THREE-LAYER STACK
  Build bottom-up. Assign top-down. Change one layer without touching the others.

  ┌──────────────────────────────────────────────────────────────────────┐
  │  LAYER 3 — IDENTITY                                                  │
  │  alice@company.com  ·  backend-service  ·  ci-runner@proj           │
  │  &quot;who is acting — a human, a service, or a machine&quot;                 │
  ├──────────────────────────────────────────────────────────────────────┤
  │  LAYER 2 — ROLE                                                      │
  │  BackendDeveloper  ·  DataAnalyst  ·  DeployBot  ·  S3ReadOnly      │
  │  &quot;what function does this identity serve — the job title&quot;           │
  ├──────────────────────────────────────────────────────────────────────┤
  │  LAYER 1 — POLICY                                                    │
  │  AllowS3Read  ·  AllowECRPush  ·  DenyProdDelete  ·  RequireMFA    │
  │  &quot;what is explicitly permitted or denied, under what conditions&quot;    │
  ├──────────────────────────────────────────────────────────────────────┤
  │  LAYER 0 — PERMISSION                                                │
  │  s3:GetObject  ·  ecr:PutImage  ·  s3:DeleteObject  ·  iam:PassRole│
  │  &quot;one verb on one class of resource — the atom of access control&quot;  │
  └──────────────────────────────────────────────────────────────────────┘

  When alice joins the backend team → assign her the BackendDeveloper role
  When the S3 bucket changes → update the policy once; alice gets it automatically
  When alice leaves → remove the role assignment; policy and permissions are untouched
</code></pre>
<p>If this maps better to something physical:</p>
<pre><code class="" data-line="">  PHYSICAL WORLD            →    CLOUD IAM

  A specific door rule           Permission      s3:GetObject
  Keycard access profile    →    Policy          AllowS3Read
  Job title                 →    Role            BackendDeveloper
  The employee              →    Identity        alice@company.com

  When the employee leaves: revoke the role assignment.
  The job title, the keycard profile, the door rules — all unchanged.
  Next hire gets the same role. Same access. No manual work.
</code></pre>
<hr />
<h2 id="introduction">Introduction</h2>
<p>IAM roles vs policies is a distinction that defines how cloud authorization actually works — and getting it wrong is how access sprawl starts. Every <a href="/authentication-vs-authorization-iam/">authentication vs authorization</a> failure at the authorization layer traces back to how these three primitives are — or aren&#8217;t — structured.</p>
<p>Every cloud IAM system — AWS, GCP, Azure — is built on the same three primitives: permissions, policies, and roles. Learn these well and any cloud provider becomes readable. Skip them and you spend years pattern-matching without understanding why anything is structured the way it is.</p>
<p><a href="/what-is-cloud-iam/">What Is Cloud IAM</a> established the foundation: IAM is the system that governs who can access what in cloud infrastructure, and its default answer is always deny. <a href="/authentication-vs-authorization-iam/">Authentication vs Authorization: AWS AccessDenied Explained</a> drew the line between authentication — proving identity — and authorization — proving you&#8217;re allowed to act. This episode is about the authorization layer specifically. These three building blocks are how authorization is expressed in practice.</p>
<p>Before walking through each one, here&#8217;s what access control looks like without any of this structure — because that&#8217;s the fastest way to understand why the layers exist.</p>
<p>In 2015 I inherited an AWS account from a 12-engineer team that had been building for 18 months. When I ran <code class="" data-line="">aws iam list-attached-user-policies</code> across the 23 users, 17 had policies attached directly to the user object — not to groups, not to roles.</p>
<p>One engineer had left six months earlier. His access key was still active. Three policies still attached: read access to prod S3, write to a DynamoDB table, ability to invoke Lambda functions. When I asked what the DynamoDB table was for, nobody could tell me. The Lambda functions no longer existed.</p>
<p>That account wasn&#8217;t built by negligent engineers. It was built by engineers reaching for whatever granted access fastest, under deadline, without a framework. Permissions scattered. Nothing tracked. Nothing removed.</p>
<p>Roles, policies, and permissions are the framework that prevents that. Understanding them is the difference between an IAM configuration you can audit in an afternoon and one that takes a week and still leaves you uncertain.</p>
<hr />
<h2 id="what-are-iam-permissions-the-atomic-unit-of-access-control">What Are IAM Permissions? The Atomic Unit of Access Control</h2>
<p>A <strong>permission</strong> is a single action on a class of resources. It is the most granular thing you can grant or deny — the atom of access control.</p>
<p>Cloud providers express permissions differently, but the structure is consistent: a service, a resource type, and an action verb.</p>
<pre><code class="" data-line=""># AWS: service:Action
s3:GetObject               # read an object from S3
ec2:StartInstances         # start EC2 instances
iam:PassRole               # assign a role to an AWS service — one of the most dangerous
kms:Decrypt                # use a KMS key to decrypt

# GCP: service.resource.verb
storage.objects.get
compute.instances.start
iam.serviceAccounts.actAs  # impersonate a service account — equivalent risk to iam:PassRole
cloudkms.cryptoKeyVersions.useToDecrypt

# Azure: Provider/ResourceType/Action
Microsoft.Storage/storageAccounts/blobServices/containers/read
Microsoft.Compute/virtualMachines/start/action
Microsoft.Authorization/roleAssignments/write   # grant roles — highest risk
Microsoft.KeyVault/vaults/secrets/getSecret/action
</code></pre>
<p>You generally don&#8217;t assign individual permissions directly to identities — that&#8217;s like handing someone 47 keys with no labels and expecting the system to remain auditable. Permissions are grouped into policies.</p>
<hr />
<h2 id="what-are-iam-policies-grouping-permissions-with-conditions">What Are IAM Policies? Grouping Permissions with Conditions</h2>
<p>A <strong>policy</strong> is a document that groups permissions and defines the conditions under which they apply.</p>
<h3 id="aws-policy-structure">AWS policy structure</h3>
<p>An AWS policy document is JSON. Every field is a deliberate decision:</p>
<pre><code class="" data-line="">{
  &quot;Version&quot;: &quot;2012-10-17&quot;,
  &quot;Statement&quot;: [
    {
      &quot;Sid&quot;: &quot;AllowReadS3Backups&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:::company-backups&quot;,
        &quot;arn:aws:s3:::company-backups/*&quot;
      ],
      &quot;Condition&quot;: {
        &quot;StringEquals&quot;: { &quot;s3:prefix&quot;: [&quot;2024/&quot;, &quot;2025/&quot;] }
      }
    },
    {
      &quot;Sid&quot;: &quot;DenyDeleteEverywhere&quot;,
      &quot;Effect&quot;: &quot;Deny&quot;,
      &quot;Action&quot;: &quot;s3:DeleteObject&quot;,
      &quot;Resource&quot;: &quot;*&quot;
    }
  ]
}
</code></pre>
<p>The <code class="" data-line="">Sid</code> is a comment — use it. <code class="" data-line="">AllowReadS3Backups</code> tells a future auditor why this statement exists. <code class="" data-line="">Statement1</code> is technical debt.</p>
<p>The <code class="" data-line="">Effect</code> is either <code class="" data-line="">Allow</code> or <code class="" data-line="">Deny</code>. A <code class="" data-line="">Deny</code> always wins — it cannot be overridden by any <code class="" data-line="">Allow</code> anywhere in any policy on the same identity. If you have a <code class="" data-line="">Deny</code> on <code class="" data-line="">s3:DeleteObject</code> with <code class="" data-line="">&quot;Resource&quot;: &quot;*&quot;</code>, nothing can grant delete access to that identity. This asymmetry is deliberate: it&#8217;s how guardrails work.</p>
<p>The <code class="" data-line="">Resource</code> field is where access most often creeps wider than intended. <code class="" data-line="">&quot;Resource&quot;: &quot;*&quot;</code> on a write action means &#8220;every resource of this type in the account.&#8221; It works. It outlives the context that made it feel reasonable.</p>
<h3 id="aws-policy-types-which-to-reach-for">AWS policy types — which to reach for</h3>
<pre><code class="" data-line="">┌──────────────────────────┬────────────────────────────┬────────────────────────────┐
│ Type                     │ Attached to                │ What it does               │
├──────────────────────────┼────────────────────────────┼────────────────────────────┤
│ Identity-based           │ User, Group, Role          │ What the identity can do   │
│ Resource-based           │ S3 bucket, KMS key, Lambda │ Who can touch this resource │
│ Permissions boundary     │ User or Role               │ Maximum possible — ceiling  │
│ Service Control Policy   │ AWS Org OU or Account      │ Org-level guardrail         │
│ Session policy           │ AssumeRole session         │ Restricts a specific session│
│ Resource Control Policy  │ AWS Org resources          │ Resource-level org guardrail│
└──────────────────────────┴────────────────────────────┴────────────────────────────┘
</code></pre>
<p>Critical: <strong>Permissions boundaries and SCPs do not grant permissions</strong>. They constrain them. A boundary that allows <code class="" data-line="">s3:*</code> doesn&#8217;t mean the identity has S3 access. It means the identity <em>can have at most</em> S3 access, if an identity-based policy actually grants it. Many engineers set a boundary and expect it to work as a grant. It doesn&#8217;t.</p>
<h3 id="gcp-policy-bindings">GCP policy bindings</h3>
<p>GCP doesn&#8217;t attach policy documents to identities. Each resource has an IAM policy — a set of <strong>bindings</strong> mapping roles to members:</p>
<pre><code class="" data-line="">{
  &quot;bindings&quot;: [
    {
      &quot;role&quot;: &quot;roles/storage.objectViewer&quot;,
      &quot;members&quot;: [
        &quot;user:alice@company.com&quot;,
        &quot;serviceAccount:app-backend@project.iam.gserviceaccount.com&quot;
      ]
    },
    {
      &quot;role&quot;: &quot;roles/storage.objectCreator&quot;,
      &quot;members&quot;: [&quot;serviceAccount:upload-service@project.iam.gserviceaccount.com&quot;],
      &quot;condition&quot;: {
        &quot;title&quot;: &quot;Business hours only&quot;,
        &quot;expression&quot;: &quot;request.time.getHours(&#039;America/New_York&#039;) &gt;= 9 &amp;&amp; request.time.getHours(&#039;America/New_York&#039;) &lt; 18&quot;
      }
    }
  ]
}
</code></pre>
<p>The mental model shift: in AWS you ask &#8220;what can this identity do?&#8221; by looking at the identity. In GCP you ask &#8220;who can access this resource?&#8221; by looking at the resource. The question runs in the opposite direction.</p>
<h3 id="azure-role-definitions">Azure role definitions</h3>
<p>Azure separates what a role grants (role definition) from who gets it where (role assignment). Define once, assign at multiple scopes.</p>
<pre><code class="" data-line="">{
  &quot;Name&quot;: &quot;Custom Storage Reader&quot;,
  &quot;IsCustom&quot;: true,
  &quot;Actions&quot;: [
    &quot;Microsoft.Storage/storageAccounts/blobServices/containers/read&quot;,
    &quot;Microsoft.Storage/storageAccounts/blobServices/generateUserDelegationKey/action&quot;
  ],
  &quot;DataActions&quot;: [
    &quot;Microsoft.Storage/storageAccounts/blobServices/containers/blobs/read&quot;
  ],
  &quot;AssignableScopes&quot;: [&quot;/subscriptions/SUB_ID&quot;]
}
</code></pre>
<p><code class="" data-line="">Actions</code> vs <code class="" data-line="">DataActions</code> catches people. <code class="" data-line="">Actions</code> are control plane — you can see the storage account exists. <code class="" data-line="">DataActions</code> are data plane — you can read actual blob contents. A user with <code class="" data-line="">Actions</code> can list the container but cannot read a single byte without a <code class="" data-line="">DataAction</code>. Both planes must be covered for the access to be complete.</p>
<hr />
<h2 id="what-are-iam-roles-the-layer-that-scales-access-control">What Are IAM Roles? The Layer That Scales Access Control</h2>
<p>A <strong>role</strong> is a collection of policies assigned to identities. It&#8217;s the indirection layer that makes access manageable at scale.</p>
<p>Going back to the 2015 account: the problem wasn&#8217;t that engineers had access — they needed it. The problem was that access was scattered across 23 individual user objects with no shared structure. This is what <a href="/what-is-cloud-iam/">what is cloud IAM</a> establishes as the core problem IAM exists to solve. Roles are the structural answer.</p>
<p>The role model solves this:</p>
<pre><code class="" data-line="">Policy: S3ReadAccess (s3:GetObject, s3:ListBucket on s3:::app-data/*)
  ↓ attached to
Role: BackendDeveloper
  ↓ assigned to
Users: alice, bob, charlie, dave (and six more)

When the bucket changes  → update one policy
When someone joins       → assign one role
When someone leaves      → remove one role
Access model stays coherent because it&#039;s structured.
</code></pre>
<h3 id="aws-roles-the-identity-that-issues-temporary-credentials">AWS roles — the identity that issues temporary credentials</h3>
<p>AWS roles are themselves IAM identities, not just permission containers. When something assumes a role, it gets temporary credentials from STS. Two things must be configured:</p>
<p><strong>Trust policy — who can assume:</strong></p>
<pre><code class="" data-line="">{
  &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;
  }]
}
</code></pre>
<p>Without this, nobody can use the role regardless of its permissions. The trust policy is the gatekeeper.</p>
<p><strong>Permission policy — what it can do:</strong></p>
<pre><code class="" data-line="">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
</code></pre>
<p>When debugging &#8220;why can&#8217;t this Lambda/EC2/ECS task do X?&#8221;, the first thing I check is the trust policy. Many times the permission policy is correct — the service simply isn&#8217;t in the trust policy and cannot assume the role at all.</p>
<h3 id="gcp-role-types">GCP role types</h3>
<pre><code class="" data-line="">┌──────────────────┬──────────────────────────────┬──────────────────────────────────┐
│ Type             │ Example                      │ When to use                      │
├──────────────────┼──────────────────────────────┼──────────────────────────────────┤
│ Basic/Primitive  │ roles/editor, roles/owner    │ Never in production              │
│ Predefined       │ roles/storage.objectViewer   │ Default — service-specific       │
│ Custom           │ Your org defines             │ When predefined is too broad     │
└──────────────────┴──────────────────────────────┴──────────────────────────────────┘
</code></pre>
<p><code class="" data-line="">roles/editor</code> at the project level grants write access to almost every GCP service. I&#8217;ve seen it granted &#8220;temporarily&#8221; and found it attached six months later. Always use predefined roles.</p>
<pre><code class="" data-line=""># Find the right predefined role
gcloud iam roles list --filter=&quot;name:roles/storage&quot; --format=&quot;table(name,title)&quot;

# See exactly what permissions it includes
gcloud iam roles describe roles/storage.objectViewer

# Create a custom role when predefined is still too broad
cat &gt; custom-log-reader.yaml &lt;&lt; &#039;EOF&#039;
title: &quot;Log Reader&quot;
description: &quot;Read application logs — nothing else&quot;
stage: &quot;GA&quot;
includedPermissions:
  - logging.logEntries.list
  - logging.logs.list
  - logging.logMetrics.get
EOF
gcloud iam roles create LogReader --project=my-project --file=custom-log-reader.yaml
</code></pre>
<h3 id="azure-built-in-and-custom-roles">Azure built-in and custom roles</h3>
<pre><code class="" data-line=""># List built-in roles containing &quot;Storage&quot;
az role definition list --output table | grep Storage

# View what a built-in role grants
az role definition list --name &quot;Storage Blob Data Reader&quot;

# Create a custom role
az role definition create --role-definition custom-app-storage.json

# Assign at a specific scope
az role assignment create \
  --assignee alice@company.com \
  --role &quot;Storage Blob Data Reader&quot; \
  --scope /subscriptions/SUB_ID/resourceGroups/rg-prod/providers/\
Microsoft.Storage/storageAccounts/prodstore
</code></pre>
<hr />
<h2 id="rbac-vs-abac-which-access-control-model-to-use">RBAC vs ABAC: Which Access Control Model to Use</h2>
<h3 id="rbac-role-based-access-control">RBAC — Role-Based Access Control</h3>
<p>The dominant model. Access flows from role membership:</p>
<pre><code class="" data-line="">alice     ∈ BackendDeveloper  →  s3:GetObject on app-data/*
bob       ∈ DataAnalyst       →  athena:* on analytics-queries
ci-runner ∈ DeployRole        →  ecr:PutImage, ecs:UpdateService
</code></pre>
<p>RBAC degrades two ways: <strong>role explosion</strong> (200 roles, nobody can explain what they all do) and <strong>coarse roles</strong> (avoid explosion by making roles broad, now BackendDeveloper has prod access with no distinction from dev). Both look the same on a spreadsheet — lots of access, no clear principle.</p>
<h3 id="abac-attribute-based-access-control">ABAC — Attribute-Based Access Control</h3>
<p>ABAC grants access based on attributes of the principal, resource, or environment — not role membership. This one policy replaced 12 team-specific policies in one account:</p>
<pre><code class="" data-line="">{
  &quot;Effect&quot;: &quot;Allow&quot;,
  &quot;Action&quot;: &quot;ec2:*&quot;,
  &quot;Resource&quot;: &quot;*&quot;,
  &quot;Condition&quot;: {
    &quot;StringEquals&quot;: {
      &quot;aws:ResourceTag/Team&quot;: &quot;${aws:PrincipalTag/Team}&quot;
    }
  }
}
</code></pre>
<p>An engineer tagged <code class="" data-line="">Team=Platform</code> can only act on EC2 resources tagged <code class="" data-line="">Team=Platform</code>. Add a new team — tag their resources and their identity. No new policy. No new role.</p>
<p>The risk is tag drift. If someone tags a resource incorrectly, the access model breaks silently. In practice, I use ABAC for environment and team scoping, and explicit policies for sensitive services like KMS and IAM. How these primitives combine in a full AWS account is covered in the <a href="/aws-iam-deep-dive/">AWS IAM deep dive</a>.</p>
<h3 id="conditions-when-context-determines-access">Conditions — when context determines access</h3>
<pre><code class="" data-line="">// Require MFA for any IAM or Organizations action
{
  &quot;Effect&quot;: &quot;Deny&quot;,
  &quot;Action&quot;: [&quot;iam:*&quot;, &quot;organizations:*&quot;],
  &quot;Resource&quot;: &quot;*&quot;,
  &quot;Condition&quot;: { &quot;BoolIfExists&quot;: { &quot;aws:MultiFactorAuthPresent&quot;: &quot;false&quot; } }
}

// Restrict to corporate IP range
{
  &quot;Effect&quot;: &quot;Deny&quot;,
  &quot;Action&quot;: &quot;*&quot;,
  &quot;Resource&quot;: &quot;*&quot;,
  &quot;Condition&quot;: {
    &quot;NotIpAddress&quot;: { &quot;aws:SourceIp&quot;: [&quot;10.0.0.0/8&quot;, &quot;172.16.0.0/12&quot;] }
  }
}
</code></pre>
<p>The MFA condition is in every account I manage. A compromised API key without an MFA session can&#8217;t escalate IAM privileges — the Deny blocks it at the condition level. This single statement meaningfully reduces the blast radius of a credential compromise.</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 — Policies attached directly to users                 ║
║                                                                      ║
║  Feels fast. Creates the exact problem from 2015: access scattered  ║
║  across individual user objects with no shared structure.            ║
║  When the user leaves, their policies don&#039;t follow — they stay.     ║
║                                                                      ║
║  Fix: always use roles. Attach policies to roles. Assign roles to   ║
║  users. The role outlives the person.                               ║
╚══════════════════════════════════════════════════════════════════════╝

╔══════════════════════════════════════════════════════════════════════╗
║  &#x26a0;  GOTCHA 2 — Using AWS managed policies in production            ║
║                                                                      ║
║  AmazonS3FullAccess grants s3:* on *. For a Lambda that reads one  ║
║  specific bucket, that&#039;s ~30 permissions you didn&#039;t need, all live. ║
║                                                                      ║
║  Fix: create customer managed policies scoped to the specific       ║
║  actions and ARNs the workload actually uses.                       ║
╚══════════════════════════════════════════════════════════════════════╝

╔══════════════════════════════════════════════════════════════════════╗
║  &#x26a0;  GOTCHA 3 — iam:PassRole with &quot;Resource&quot;: &quot;*&quot;                   ║
║                                                                      ║
║  iam:PassRole lets an identity assign a role to an AWS service.     ║
║  With Resource: *, it can pass ANY role — including ones with more  ║
║  permissions than it currently has. That is a privilege escalation. ║
║                                                                      ║
║  Fix: always scope iam:PassRole to a specific role ARN:             ║
║  &quot;Resource&quot;: &quot;arn:aws:iam::ACCOUNT:role/SpecificRoleName&quot;          ║
╚══════════════════════════════════════════════════════════════════════╝

╔══════════════════════════════════════════════════════════════════════╗
║  &#x26a0;  GOTCHA 4 — Permissions boundary ≠ policy grant                 ║
║                                                                      ║
║  Setting a boundary that allows s3:* does NOT grant S3 access.     ║
║  The boundary is a ceiling — it limits maximum possible permissions. ║
║  The identity-based policy still needs to explicitly Allow the      ║
║  action. Both must be present for the access to work.               ║
╚══════════════════════════════════════════════════════════════════════╝
</code></pre>
<hr />
<h2 id="cross-cloud-rosetta-stone">Cross-Cloud Rosetta Stone</h2>
<p>Same concepts, different names and different directions. Bookmark this table.</p>
<pre><code class="" data-line="">┌─────────────────────────┬──────────────────────────┬──────────────────────────┬──────────────────────────┐
│ Concept                 │ AWS                      │ GCP                      │ Azure                    │
├─────────────────────────┼──────────────────────────┼──────────────────────────┼──────────────────────────┤
│ Atomic permission       │ s3:GetObject             │ storage.objects.get      │ .../blobs/read           │
│ Permission document     │ Policy (JSON)            │ (built into role def)    │ Role Definition          │
│ Access grant            │ Policy attachment        │ IAM Binding              │ Role Assignment          │
│ Job-function identity   │ IAM Role                 │ Predefined Role          │ Built-in Role            │
│ Non-human identity      │ IAM Role (assumed)       │ Service Account          │ Managed Identity         │
│ Org-level guardrail     │ SCP                      │ Org Policy               │ Management Group Policy  │
│ Permission ceiling      │ Permissions Boundary     │ —                        │ —                        │
│ Session restriction     │ Session Policy           │ —                        │ —                        │
│ Attribute-based grant   │ Tag conditions in policy │ IAM Conditions           │ Conditions in assignment │
└─────────────────────────┴──────────────────────────┴──────────────────────────┴──────────────────────────┘
</code></pre>
<hr />
<h2 id="quick-reference">Quick Reference</h2>
<pre><code class="" data-line="">┌──────────────────────────┬────────────────────────────────────────────────────────────┐
│ Term                     │ What it is                                                 │
├──────────────────────────┼────────────────────────────────────────────────────────────┤
│ Permission               │ Atomic: one action on one resource class                   │
│ Policy                   │ Document grouping permissions + conditions                 │
│ Role (AWS)               │ Assumable identity — carries policies, issues temp creds   │
│ Trust policy (AWS)       │ Who can assume this role — separate from permissions       │
│ Permissions boundary     │ Ceiling — limits max possible permissions; does not grant  │
│ SCP                      │ Org guardrail — constrains all identities in scope         │
│ IAM Binding (GCP)        │ Maps a role to a member on a specific resource             │
│ Role Assignment (Azure)  │ Grants a role definition at a specific scope               │
│ ABAC                     │ Access by tag/attribute — one policy replaces many roles   │
│ RBAC                     │ Access by role membership — clean until roles proliferate  │
│ iam:PassRole             │ Privilege escalation vector — always scope to specific ARN │
└──────────────────────────┴────────────────────────────────────────────────────────────┘

Commands to know:
┌────────────────────────────────────────────────────────────────────────────────┐
│  # AWS — list policies attached to a role                                     │
│  aws iam list-attached-role-policies --role-name MyRole                       │
│                                                                                │
│  # AWS — view what a managed policy actually grants                           │
│  aws iam get-policy-version \                                                  │
│    --policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess \              │
│    --version-id v1                                                             │
│                                                                                │
│  # AWS — who can assume this role?                                            │
│  aws iam get-role --role-name MyRole --query &#039;Role.AssumeRolePolicyDocument&#039;  │
│                                                                                │
│  # GCP — view the IAM policy on a project                                    │
│  gcloud projects get-iam-policy PROJECT_ID --format=json                      │
│                                                                                │
│  # GCP — list all roles and what permissions they include                    │
│  gcloud iam roles describe roles/storage.objectViewer                         │
│                                                                                │
│  # Azure — list role assignments in a subscription                           │
│  az role assignment list --all --output table                                 │
│                                                                                │
│  # Azure — view exactly what a built-in role grants                          │
│  az role definition list --name &quot;Storage Blob Data Reader&quot;                   │
└────────────────────────────────────────────────────────────────────────────────┘
</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>RBAC and ABAC are the implementation models for authorization at scale</td>
</tr>
<tr>
<td>CISSP</td>
<td>Domain 1 — Security &amp; Risk Management</td>
<td>Role design implements separation of duties and least privilege</td>
</tr>
<tr>
<td>ISO 27001:2022</td>
<td>5.15 Access control</td>
<td>Access control policy — roles and policies are the mechanism</td>
</tr>
<tr>
<td>ISO 27001:2022</td>
<td>5.18 Access rights</td>
<td>Provisioning, review, and removal of access rights — roles make this auditable</td>
</tr>
<tr>
<td>ISO 27001:2022</td>
<td>8.2 Privileged access rights</td>
<td>Permissions boundaries and conditions applied to elevated access</td>
</tr>
<tr>
<td>SOC 2</td>
<td>CC6.1</td>
<td>Logical access security — policy documents are the technical implementation</td>
</tr>
<tr>
<td>SOC 2</td>
<td>CC6.3</td>
<td>Access revocation — role-based model makes removal consistent and auditable</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="key-takeaways">Key Takeaways</h2>
<ul>
<li>Permissions are atomic — one action on one resource class. Policies group permissions. Roles carry policies for assignment</li>
<li>AWS roles have two required configs: trust policy (who can assume) and permission policy (what it can do) — both must be correct</li>
<li>GCP binds roles to resources; AWS attaches policies to identities — the mental model runs in opposite directions</li>
<li>Azure separates role definition (what) from role assignment (who, where) — define once, assign at multiple scopes</li>
<li>RBAC scales through role design; ABAC scales through tag/attribute conditions — use ABAC where roles would proliferate</li>
<li><code class="" data-line="">iam:PassRole</code> and <code class="" data-line="">iam.serviceAccounts.actAs</code> are privilege escalation vectors — scope them to specific ARNs, never <code class="" data-line="">*</code></li>
<li>Conditions add context (MFA, IP, tags, time) to policies — the MFA condition on IAM actions is essential in every account</li>
</ul>
<hr />
<h2 id="whats-next">What&#8217;s Next</h2>
<p>EP04 goes deep on AWS IAM — the most complex of the three cloud models. Policy evaluation order, cross-account trust, permissions boundaries in practice, SCPs, and IAM Identity Center for human access. We&#8217;ll work through the patterns that make AWS IAM maintainable at production scale.</p>
<p><em>Next: <a href="/aws-iam-deep-dive/">AWS IAM Deep Dive: Users, Groups, Roles, and Policies Explained</a></em></p>
<p>Get the AWS IAM deep dive 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%2Fiam-roles-policies-permissions-explained%2F&amp;linkname=IAM%20Roles%20vs%20Policies%3A%20How%20Cloud%20Authorization%20Actually%20Works" 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%2Fiam-roles-policies-permissions-explained%2F&amp;linkname=IAM%20Roles%20vs%20Policies%3A%20How%20Cloud%20Authorization%20Actually%20Works" 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%2Fiam-roles-policies-permissions-explained%2F&amp;linkname=IAM%20Roles%20vs%20Policies%3A%20How%20Cloud%20Authorization%20Actually%20Works" 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%2Fiam-roles-policies-permissions-explained%2F&amp;linkname=IAM%20Roles%20vs%20Policies%3A%20How%20Cloud%20Authorization%20Actually%20Works" 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%2Fiam-roles-policies-permissions-explained%2F&amp;linkname=IAM%20Roles%20vs%20Policies%3A%20How%20Cloud%20Authorization%20Actually%20Works" 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%2Fiam-roles-policies-permissions-explained%2F&amp;linkname=IAM%20Roles%20vs%20Policies%3A%20How%20Cloud%20Authorization%20Actually%20Works" 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%2Fiam-roles-policies-permissions-explained%2F&amp;linkname=IAM%20Roles%20vs%20Policies%3A%20How%20Cloud%20Authorization%20Actually%20Works" 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%2Fiam-roles-policies-permissions-explained%2F&#038;title=IAM%20Roles%20vs%20Policies%3A%20How%20Cloud%20Authorization%20Actually%20Works" data-a2a-url="https://linuxcent.com/iam-roles-policies-permissions-explained/" data-a2a-title="IAM Roles vs Policies: How Cloud Authorization Actually Works"></a></p><p>The post <a href="https://linuxcent.com/iam-roles-policies-permissions-explained/">IAM Roles vs Policies: How Cloud Authorization Actually Works</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://linuxcent.com/iam-roles-policies-permissions-explained/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1458</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-03 04:20:03 by W3 Total Cache
-->