<?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>Cross Account Access Archives - Linuxcent</title>
	<atom:link href="https://linuxcent.com/tag/cross-account-access/feed/" rel="self" type="application/rss+xml" />
	<link>https://linuxcent.com/tag/cross-account-access/</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</generator>

<image>
	<url>https://linuxcent.com/wp-content/uploads/2026/04/favicon-512x512-1-150x150.png</url>
	<title>Cross Account Access Archives - Linuxcent</title>
	<link>https://linuxcent.com/tag/cross-account-access/</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>
	</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-07-07 01:19:11 by W3 Total Cache
-->