🛡 IAM Policy Inheritance in AWS
AWS uses IAM policies to control permissions.
With AWS Identity and Access Management (IAM), you define who can do what, on which resources.
Understanding how policies are inherited is key to managing permissions securely — especially in real-world AWS setups.
Let's break it down step by step.
👥 Understanding IAM Policy Inheritance
Imagine a company structure with multiple users and groups.
🧑💻 Developers Group
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>IAM Policy Inheritance</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Newsreader:ital,wght@0,500;0,600;1,500&family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
.infographic-groupinherit {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f0ece4;
--accent: #2563eb;
--accent-soft:#e3ebfd;
--line: #ddd6c9;
--good: #1a9c6d;
--radius: 10px;
--display-font: 'Newsreader', serif;
--body-font: 'Inter', sans-serif;
background: var(--paper);
color: var(--ink);
font-family: var(--body-font);
max-width: 880px;
margin: 0 auto;
box-sizing: border-box;
}
.infographic-groupinherit[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #121212;
--paper-alt: #1e1e26;
--accent: #5b8def;
--accent-soft:#202942;
--line: #2c313d;
--good: #4fd39c;
}
.infographic-groupinherit * { box-sizing: border-box; }
.infographic-groupinherit .kicker {
color: var(--accent);
font-size: 0.8rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.08em;
margin: 0 0 0.5rem;
}
.infographic-groupinherit h1 {
font-family: var(--display-font);
font-weight: 600;
font-size: clamp(1.5rem, 4vw, 2.1rem);
margin: 0 0 0.35rem;
letter-spacing: -0.01em;
}
.infographic-groupinherit .sub {
color: var(--ink-soft);
font-size: 0.95rem;
margin: 0 0 1.75rem;
max-width: 60ch;
}
.gi-diagram {
border: 1px solid var(--line);
border-radius: var(--radius);
padding: 1.5rem clamp(0.75rem, 3vw, 2rem) 1rem;
background: var(--paper-alt);
}
.gi-svg { width: 100%; height: auto; display: block; }
.gi-group-box { fill: var(--paper); stroke: var(--accent); stroke-width: 2; }
.gi-group-label { font-family: var(--body-font); font-weight: 700; fill: var(--ink); font-size: 15px; }
.gi-policy-card { fill: var(--paper); stroke: var(--accent); stroke-width: 2; cursor: pointer; }
.gi-policy-card.is-active { fill: var(--accent-soft); }
.gi-check { stroke: var(--good); stroke-width: 3; fill: none; stroke-linecap: round; stroke-linejoin: round; }
.gi-x { stroke: #d9534f; stroke-width: 3; fill: none; stroke-linecap: round; }
.gi-link { stroke: var(--line); stroke-width: 2; transition: stroke 0.15s, stroke-width 0.15s; }
.gi-link.is-lit { stroke: var(--accent); stroke-width: 3; }
.gi-user { cursor: pointer; }
.gi-user circle, .gi-user path { stroke: var(--ink-soft); stroke-width: 2.5; fill: none; transition: stroke 0.15s, opacity 0.15s; }
.gi-user.is-active circle, .gi-user.is-active path { stroke: var(--accent); }
.gi-user.is-dimmed { opacity: 0.4; }
.gi-user-label { font-family: var(--body-font); font-weight: 600; fill: var(--ink); font-size: 14px; text-anchor: middle; transition: fill 0.15s; }
.gi-user.is-active + .gi-user-label,
.gi-user-label.is-active { fill: var(--accent); }
.gi-badge { font-family: var(--body-font); font-size: 11px; fill: var(--good); font-weight: 700; opacity: 0; transition: opacity 0.15s; text-anchor: middle; }
.gi-badge.is-shown { opacity: 1; }
.gi-detail {
margin-top: 1.25rem;
padding: 0.85rem 1.1rem;
background: var(--paper);
border: 1px solid var(--line);
border-radius: var(--radius);
font-size: 0.9rem;
color: var(--ink-soft);
min-height: 1.2em;
}
.gi-detail strong { color: var(--ink); }
.gi-takeaway {
margin-top: 1.25rem;
font-size: 0.95rem;
color: var(--ink);
padding-left: 1.6rem;
position: relative;
}
.gi-takeaway::before {
content: "👉";
position: absolute;
left: 0;
}
@media (max-width: 480px) {
.gi-diagram { padding: 1rem 0.5rem; }
}
</style>
</head>
<body>
<div class="infographic-groupinherit" id="gi-root">
<div class="gi-diagram">
<svg class="gi-svg" viewBox="0 0 680 460" xmlns="http://www.w3.org/2000/svg">
<!-- group container -->
<rect class="gi-group-box" x="20" y="20" width="640" height="410" rx="16"></rect>
<text class="gi-group-label" x="44" y="52">Developers Group</text>
<!-- policy card -->
<g class="gi-policy-card-group" data-role="policy" tabindex="0">
<rect class="gi-policy-card" x="300" y="80" width="80" height="96" rx="10"></rect>
<path class="gi-check" d="M318 108 L326 116 L342 98"></path>
<path class="gi-check" d="M318 128 L326 136 L342 118"></path>
<path class="gi-x" d="M317 148 L333 162 M333 148 L317 162"></path>
</g>
<!-- links -->
<line class="gi-link" data-link="alice" x1="340" y1="176" x2="140" y2="280"></line>
<line class="gi-link" data-link="bob" x1="340" y1="176" x2="340" y2="280"></line>
<line class="gi-link" data-link="charles" x1="340" y1="176" x2="540" y2="280"></line>
<!-- Alice -->
<g class="gi-user" data-user="alice" tabindex="0">
<circle cx="140" cy="300" r="20"></circle>
<path d="M108 356 a32 32 0 0 1 64 0"></path>
</g>
<text class="gi-badge" data-badge="alice" x="140" y="260">inherits ✓</text>
<text class="gi-user-label" data-label="alice" x="140" y="408">Alice</text>
<!-- Bob -->
<g class="gi-user" data-user="bob" tabindex="0">
<circle cx="340" cy="300" r="20"></circle>
<path d="M308 356 a32 32 0 0 1 64 0"></path>
</g>
<text class="gi-badge" data-badge="bob" x="340" y="260">inherits ✓</text>
<text class="gi-user-label" data-label="bob" x="340" y="408">Bob</text>
<!-- Charles -->
<g class="gi-user" data-user="charles" tabindex="0">
<circle cx="540" cy="300" r="20"></circle>
<path d="M508 356 a32 32 0 0 1 64 0"></path>
</g>
<text class="gi-badge" data-badge="charles" x="540" y="260">inherits ✓</text>
<text class="gi-user-label" data-label="charles" x="540" y="408">Charles</text>
</svg>
</div>
<div class="gi-detail" id="gi-detail">Click the policy card above to attach it to the group.</div>
<p class="gi-takeaway">You don't attach the same policy to each user individually — attach it once to the group.</p>
</div>
<script>
(function () {
const wrapper = document.getElementById('gi-root');
// dark mode detection
const bodyAttr = document.body.getAttribute('data-darkmode');
let dark;
if (bodyAttr === 'true') {
dark = true;
} else if (bodyAttr === 'false') {
dark = false;
} else {
const match = document.cookie.match(/(?:^|;\s*)darkMode=([^;]+)/);
dark = match ? match[1] === 'true' : false;
}
if (dark) wrapper.setAttribute('data-theme', 'dark');
const policyCard = wrapper.querySelector('[data-role="policy"] .gi-policy-card');
const links = wrapper.querySelectorAll('.gi-link');
const users = wrapper.querySelectorAll('.gi-user');
const labels = wrapper.querySelectorAll('.gi-user-label');
const badges = wrapper.querySelectorAll('.gi-badge');
const detail = document.getElementById('gi-detail');
const names = { alice: 'Alice', bob: 'Bob', charles: 'Charles' };
function attachToAll() {
policyCard.classList.add('is-active');
links.forEach(l => l.classList.add('is-lit'));
users.forEach(u => { u.classList.add('is-active'); u.classList.remove('is-dimmed'); });
labels.forEach(l => l.classList.add('is-active'));
badges.forEach(b => b.classList.add('is-shown'));
detail.innerHTML = '<strong>Policy attached to the group.</strong> Alice, Bob, and Charles all inherit it automatically — no per-user setup needed.';
}
function focusUser(name) {
policyCard.classList.add('is-active');
links.forEach(l => l.classList.toggle('is-lit', l.dataset.link === name));
users.forEach(u => {
const match = u.dataset.user === name;
u.classList.toggle('is-active', match);
u.classList.toggle('is-dimmed', !match);
});
labels.forEach(l => l.classList.toggle('is-active', l.dataset.label === name));
badges.forEach(b => b.classList.toggle('is-shown', b.dataset.badge === name));
detail.innerHTML = '<strong>' + names[name] + '</strong> never had the policy attached directly — it flows in from the Developers group.';
}
wrapper.querySelector('[data-role="policy"]').addEventListener('click', attachToAll);
wrapper.querySelector('[data-role="policy"]').addEventListener('keydown', e => {
if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); attachToAll(); }
});
users.forEach(u => {
u.addEventListener('click', () => focusUser(u.dataset.user));
u.addEventListener('keydown', e => {
if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); focusUser(u.dataset.user); }
});
});
})();
</script>
</body>
</html>.demo {
font-family: system-ui, sans-serif;
padding: 16px;
}
.demo h1 {
color: #2563eb;
margin: 0 0 8px;
}
.demo p {
color: #4b5563;
margin: 0;
}document.querySelector('.demo h1')?.addEventListener('click', () => {
alert('Hello from live preview!');
});⚙ Operations Group
A policy attached to a group applies only to that group's members. Click a permission, click a person, or run the isolation check below.
<div class="infographic-iamgroups">
<style>
.infographic-iamgroups {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f6f4ef;
--accent: #2563eb; /* Developers blue */
--accent-soft:#e3ebfd;
--accent2: #d97706; /* Operations orange */
--accent2-soft:#fdecd2;
--line: #ddd6c9;
--ok: #1e8e5a;
--ok-soft: #e2f6ec;
--bad: #c0392b;
--bad-soft: #fbe9e7;
--display-font: Georgia, 'Iowan Old Style', serif;
--mono-font: 'SF Mono', Menlo, Consolas, monospace;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
color: var(--ink);
background: var(--paper);
padding: 2rem 1.25rem 2.5rem;
max-width: 880px;
margin: 0 auto;
border-radius: 14px;
box-sizing: border-box;
}
.infographic-iamgroups[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #121212;
--paper-alt: #1e1e26;
--accent: #6f9bff;
--accent-soft:#1c2740;
--accent2: #f0a94e;
--accent2-soft:#3a2b12;
--line: #2c313d;
--ok: #4fbf87;
--ok-soft: #143024;
--bad: #e57373;
--bad-soft: #3a2222;
}
.infographic-iamgroups * { box-sizing: border-box; }
.iam-eyebrow { font-size: 0.75rem; letter-spacing: 0.08em; text-transform: uppercase;
color: var(--ink-soft); margin: 0 0 0.35rem; }
.iam-title { font-family: var(--display-font); font-size: 1.7rem; margin: 0 0 0.4rem; line-height: 1.2; }
.iam-sub { color: var(--ink-soft); font-size: 0.95rem; margin: 0 0 1.75rem; max-width: 60ch; }
.iam-groups { display: grid; grid-template-columns: 1fr 1fr; gap: 1.25rem; }
@media (max-width: 640px) { .iam-groups { grid-template-columns: 1fr; } }
.iam-group { border: 1.5px solid var(--line); border-radius: 12px; padding: 1.25rem;
background: var(--paper-alt); transition: border-color 0.15s, opacity 0.15s; }
.iam-group[data-g="dev"] { border-top: 4px solid var(--accent); }
.iam-group[data-g="ops"] { border-top: 4px solid var(--accent2); }
.iam-group.is-dimmed { opacity: 0.4; }
.iam-group-name { font-weight: 700; font-size: 1.05rem; margin: 0 0 0.9rem; display: flex;
align-items: center; gap: 0.4rem; }
.iam-group[data-g="dev"] .iam-group-name { color: var(--accent); }
.iam-group[data-g="ops"] .iam-group-name { color: var(--accent2); }
.iam-policy { border: 1.5px solid; border-radius: 8px; padding: 0.7rem 0.85rem; background: var(--paper);
margin: 0 auto 1rem; max-width: 220px; }
.iam-group[data-g="dev"] .iam-policy { border-color: var(--accent); }
.iam-group[data-g="ops"] .iam-policy { border-color: var(--accent2); }
.iam-policy-label { font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.06em;
color: var(--ink-soft); margin-bottom: 0.4rem; }
.iam-perm { display: flex; align-items: center; gap: 0.45rem; font-size: 0.8rem;
padding: 0.2rem 0; cursor: pointer; border-radius: 4px; }
.iam-perm:hover { background: var(--accent-soft); }
.iam-group[data-g="ops"] .iam-perm:hover { background: var(--accent2-soft); }
.iam-perm .mark { width: 1rem; text-align: center; font-weight: 700; flex-shrink: 0; }
.iam-perm.allow .mark { color: var(--ok); }
.iam-perm.deny .mark { color: var(--bad); }
.iam-perm.is-focused { background: var(--accent-soft); }
.iam-group[data-g="ops"] .iam-perm.is-focused { background: var(--accent2-soft); }
.iam-members { display: flex; justify-content: center; gap: 1.4rem; flex-wrap: wrap; }
.iam-member { background: none; border: none; font: inherit; color: var(--ink); cursor: pointer;
display: flex; flex-direction: column; align-items: center; gap: 0.35rem; padding: 0.3rem; }
.iam-member svg { width: 42px; height: 42px; }
.iam-member circle, .iam-member path { stroke: var(--ink-soft); fill: none; stroke-width: 6;
transition: stroke 0.15s; }
.iam-member.is-active circle, .iam-member.is-active path { stroke: var(--accent); }
.iam-group[data-g="ops"] .iam-member.is-active circle,
.iam-group[data-g="ops"] .iam-member.is-active path { stroke: var(--accent2); }
.iam-member-name { font-size: 0.82rem; font-weight: 600; }
.iam-detail { margin-top: 1rem; padding-top: 0.85rem; border-top: 1px dashed var(--line);
font-size: 0.82rem; color: var(--ink-soft); min-height: 1.2em; }
.iam-detail b { color: var(--ink); }
.iam-callout { margin: 1.75rem 0; text-align: center; font-size: 0.85rem; color: var(--ink-soft);
display: flex; align-items: center; justify-content: center; gap: 0.6rem; }
.iam-callout .x { color: var(--bad); font-weight: 700; }
.iam-lab { border: 1.5px solid var(--line); border-radius: 12px; padding: 1.25rem 1.4rem;
background: var(--paper-alt); }
.iam-lab-title { font-weight: 700; margin: 0 0 0.15rem; font-size: 0.95rem; }
.iam-lab-sub { font-size: 0.8rem; color: var(--ink-soft); margin: 0 0 1rem; }
.iam-lab-row { display: flex; gap: 0.6rem; flex-wrap: wrap; align-items: center; }
.iam-lab select { font: inherit; font-size: 0.85rem; padding: 0.4rem 0.6rem; border-radius: 6px;
border: 1px solid var(--line); background: var(--paper); color: var(--ink); }
.iam-lab button { font: inherit; font-size: 0.85rem; padding: 0.45rem 0.9rem; border-radius: 6px;
border: none; background: var(--ink); color: var(--paper); cursor: pointer; }
.iam-lab button:hover { opacity: 0.85; }
.iam-lab-result { margin-top: 0.9rem; font-family: var(--mono-font); font-size: 0.82rem;
padding: 0.6rem 0.8rem; border-radius: 6px; background: var(--paper); border: 1px solid var(--line);
transition: background 0.2s, border-color 0.2s; }
.iam-lab-result.ok { background: var(--ok-soft); border-color: var(--ok); color: var(--ok); }
.iam-lab-result.bad { background: var(--bad-soft); border-color: var(--bad); color: var(--bad);
animation: iam-shake 0.4s; }
@keyframes iam-shake {
0%, 100% { transform: translateX(0); }
20% { transform: translateX(-4px); }
40% { transform: translateX(4px); }
60% { transform: translateX(-3px); }
80% { transform: translateX(3px); }
}
@media (prefers-reduced-motion: reduce) {
.infographic-iamgroups * { animation: none !important; transition-duration: 0.001ms !important; }
}
</style>
<div class="iam-groups">
<div class="iam-group" data-g="dev">
<p class="iam-group-name">👥 Developers</p>
<div class="iam-policy">
<p class="iam-policy-label">DevPolicy</p>
<div class="iam-perm allow" data-perm="s3:GetObject"><span class="mark">✓</span> Read app data (S3)</div>
<div class="iam-perm allow" data-perm="s3:PutObject"><span class="mark">✓</span> Upload builds (S3)</div>
<div class="iam-perm deny" data-perm="iam:DeleteUser"><span class="mark">✕</span> Manage users (IAM)</div>
</div>
<div class="iam-members">
<button class="iam-member" data-member="Alice" data-group="dev">
<svg viewBox="0 0 60 60"><circle cx="30" cy="18" r="12"/><path d="M8 54 a22 22 0 0 1 44 0"/></svg>
<span class="iam-member-name">Alice</span>
</button>
<button class="iam-member" data-member="Bob" data-group="dev">
<svg viewBox="0 0 60 60"><circle cx="30" cy="18" r="12"/><path d="M8 54 a22 22 0 0 1 44 0"/></svg>
<span class="iam-member-name">Bob</span>
</button>
<button class="iam-member" data-member="Charles" data-group="dev">
<svg viewBox="0 0 60 60"><circle cx="30" cy="18" r="12"/><path d="M8 54 a22 22 0 0 1 44 0"/></svg>
<span class="iam-member-name">Charles</span>
</button>
</div>
<div class="iam-detail" data-detail="dev">Click a name to see what they can do.</div>
</div>
<div class="iam-group" data-g="ops">
<p class="iam-group-name">👥 Operations</p>
<div class="iam-policy">
<p class="iam-policy-label">OpsPolicy</p>
<div class="iam-perm allow" data-perm="ec2:StartStop"><span class="mark">✓</span> Start/stop servers (EC2)</div>
<div class="iam-perm allow" data-perm="cloudwatch:GetMetrics"><span class="mark">✓</span> View monitoring</div>
<div class="iam-perm deny" data-perm="s3:DeleteBucket"><span class="mark">✕</span> Delete storage (S3)</div>
</div>
<div class="iam-members">
<button class="iam-member" data-member="David" data-group="ops">
<svg viewBox="0 0 60 60"><circle cx="30" cy="18" r="12"/><path d="M8 54 a22 22 0 0 1 44 0"/></svg>
<span class="iam-member-name">David</span>
</button>
<button class="iam-member" data-member="Edward" data-group="ops">
<svg viewBox="0 0 60 60"><circle cx="30" cy="18" r="12"/><path d="M8 54 a22 22 0 0 1 44 0"/></svg>
<span class="iam-member-name">Edward</span>
</button>
</div>
<div class="iam-detail" data-detail="ops">Click a name to see what they can do.</div>
</div>
</div>
<p class="iam-callout"><span class="x">✕</span> DevPolicy never reaches David or Edward — different container, no membership, no access.</p>
<div class="iam-lab">
<p class="iam-lab-title">Try the isolation check</p>
<p class="iam-lab-sub">Pick a person and an action. See whether their group's policy allows it.</p>
<div class="iam-lab-row">
<select id="iam-who">
<option value="Alice">Alice (Developers)</option>
<option value="Bob">Bob (Developers)</option>
<option value="Charles">Charles (Developers)</option>
<option value="David">David (Operations)</option>
<option value="Edward">Edward (Operations)</option>
</select>
<select id="iam-action">
<option value="s3:GetObject">Read app data (S3)</option>
<option value="s3:PutObject">Upload builds (S3)</option>
<option value="iam:DeleteUser">Manage users (IAM)</option>
<option value="ec2:StartStop">Start/stop servers (EC2)</option>
<option value="cloudwatch:GetMetrics">View monitoring</option>
<option value="s3:DeleteBucket">Delete storage (S3)</option>
</select>
<button id="iam-try">Attempt access</button>
</div>
<div class="iam-lab-result" id="iam-result">Result will appear here.</div>
</div>
<script>
(function () {
const wrapper = document.currentScript.closest('.infographic-iamgroups')
|| document.querySelector('.infographic-iamgroups');
const bodyAttr = document.body.getAttribute('data-darkmode');
let dark;
if (bodyAttr === 'true') { dark = true; }
else if (bodyAttr === 'false') { dark = false; }
else {
const match = document.cookie.match(/(?:^|;\s*)darkMode=([^;]+)/);
dark = match ? match[1] === 'true' : false;
}
if (dark) wrapper.setAttribute('data-theme', 'dark');
const memberGroup = { Alice: 'dev', Bob: 'dev', Charles: 'dev', David: 'ops', Edward: 'ops' };
const policies = {
dev: {
's3:GetObject': { allow: true, label: 'Read app data (S3)' },
's3:PutObject': { allow: true, label: 'Upload builds (S3)' },
'iam:DeleteUser': { allow: false, label: 'Manage users (IAM)' }
},
ops: {
'ec2:StartStop': { allow: true, label: 'Start/stop servers (EC2)' },
'cloudwatch:GetMetrics': { allow: true, label: 'View monitoring' },
's3:DeleteBucket': { allow: false, label: 'Delete storage (S3)' }
}
};
function memberSummary(group) {
const p = policies[group];
const allowed = Object.values(p).filter(x => x.allow).map(x => x.label);
const denied = Object.values(p).filter(x => !x.allow).map(x => x.label);
return { allowed, denied };
}
wrapper.querySelectorAll('.iam-member').forEach(btn => {
btn.addEventListener('click', () => {
const group = btn.dataset.group;
const name = btn.dataset.member;
const container = wrapper.querySelector('.iam-group[data-g="' + group + '"]');
container.querySelectorAll('.iam-member').forEach(m => m.classList.remove('is-active'));
btn.classList.add('is-active');
const { allowed, denied } = memberSummary(group);
const detail = wrapper.querySelector('.iam-detail[data-detail="' + group + '"]');
detail.innerHTML = '<b>' + name + '</b> inherits ' + (group === 'dev' ? 'DevPolicy' : 'OpsPolicy') +
': can ' + allowed.join(', ') + '. Cannot ' + denied.join(', ') + '.';
});
});
wrapper.querySelectorAll('.iam-perm').forEach(perm => {
perm.addEventListener('click', () => {
const group = perm.closest('.iam-group').dataset.g;
wrapper.querySelectorAll('.iam-group[data-g="' + group + '"] .iam-perm')
.forEach(p => p.classList.remove('is-focused'));
perm.classList.add('is-focused');
const other = group === 'dev' ? 'ops' : 'dev';
wrapper.querySelector('.iam-group[data-g="' + group + '"]').classList.remove('is-dimmed');
wrapper.querySelector('.iam-group[data-g="' + other + '"]').classList.add('is-dimmed');
setTimeout(() => {
wrapper.querySelectorAll('.iam-group').forEach(g => g.classList.remove('is-dimmed'));
}, 900);
});
});
const whoSel = wrapper.querySelector('#iam-who');
const actionSel = wrapper.querySelector('#iam-action');
const resultEl = wrapper.querySelector('#iam-result');
wrapper.querySelector('#iam-try').addEventListener('click', () => {
const who = whoSel.value;
const action = actionSel.value;
const group = memberGroup[who];
const inThisGroupPolicy = policies[group][action];
resultEl.classList.remove('ok', 'bad');
if (!inThisGroupPolicy) {
resultEl.textContent = who + ' \u2192 ' + action + ': DENIED — no matching statement (not this group\u2019s policy)';
resultEl.classList.add('bad');
} else if (inThisGroupPolicy.allow) {
resultEl.textContent = who + ' \u2192 ' + action + ': ALLOWED (via ' + (group === 'dev' ? 'DevPolicy' : 'OpsPolicy') + ')';
resultEl.classList.add('ok');
} else {
resultEl.textContent = who + ' \u2192 ' + action + ': DENIED — explicit deny in ' + (group === 'dev' ? 'DevPolicy' : 'OpsPolicy');
resultEl.classList.add('bad');
}
});
})();
</script>
</div>.demo {
font-family: system-ui, sans-serif;
padding: 16px;
}
.demo h1 {
color: #2563eb;
margin: 0 0 8px;
}
.demo p {
color: #4b5563;
margin: 0;
}document.querySelector('.demo h1')?.addEventListener('click', () => {
alert('Hello from live preview!');
});👤 Individual User (No Group)
Alice, Bob and Charles get permissions from a policy attached to their group. Fred belongs to no group — so his policy is attached straight to him.
<div class="infographic-iamsimple">
<style>
.infographic-iamsimple {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f6f4ef;
--accent: #2563eb;
--accent-soft:#e3ebfd;
--accent2: #d97706;
--accent2-soft:#fdecd2;
--accent3: #7c3aed;
--accent3-soft:#efe7fd;
--line: #ddd6c9;
--ok: #1e8e5a;
--bad: #c0392b;
--display-font: Georgia, 'Iowan Old Style', serif;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
color: var(--ink);
background: var(--paper);
padding: 2rem 1.25rem 2.5rem;
max-width: 900px;
margin: 0 auto;
border-radius: 14px;
box-sizing: border-box;
}
.infographic-iamsimple[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #121212;
--paper-alt: #1e1e26;
--accent: #6f9bff;
--accent-soft:#1c2740;
--accent2: #f0a94e;
--accent2-soft:#3a2b12;
--accent3: #b18cf5;
--accent3-soft:#2a2140;
--line: #2c313d;
--ok: #4fbf87;
--bad: #e57373;
}
.infographic-iamsimple * { box-sizing: border-box; }
.iam-eyebrow { font-size: 0.75rem; letter-spacing: 0.08em; text-transform: uppercase;
color: var(--ink-soft); margin: 0 0 0.35rem; }
.iam-title { font-family: var(--display-font); font-size: 1.6rem; margin: 0 0 0.4rem; line-height: 1.2; }
.iam-sub { color: var(--ink-soft); font-size: 0.92rem; margin: 0 0 1.75rem; max-width: 60ch;text-align: center }
.iam-row { display: grid; grid-template-columns: 1fr 1fr 0.7fr; gap: 1.25rem; }
@media (max-width: 700px) { .iam-row { grid-template-columns: 1fr; } }
.iam-card { border: 1.5px solid var(--line); border-radius: 12px; padding: 1.25rem;
background: var(--paper-alt); text-align: center; }
.iam-card[data-g="dev"] { border-top: 4px solid var(--accent); }
.iam-card[data-g="ops"] { border-top: 4px solid var(--accent2); }
.iam-card[data-g="fred"] { border: 1.5px dashed var(--accent3); background: transparent; }
.iam-card-name { font-weight: 700; font-size: 1rem; margin: 0 0 0.9rem; }
.iam-card[data-g="dev"] .iam-card-name { color: var(--accent); }
.iam-card[data-g="ops"] .iam-card-name { color: var(--accent2); }
.iam-card[data-g="fred"] .iam-card-name { color: var(--accent3); }
.iam-policy-box { display: inline-flex; flex-direction: column; gap: 0.15rem; border: 1.5px solid;
border-radius: 8px; padding: 0.6rem 0.9rem; background: var(--paper); margin-bottom: 0.9rem; }
.iam-card[data-g="dev"] .iam-policy-box { border-color: var(--accent); }
.iam-card[data-g="ops"] .iam-policy-box { border-color: var(--accent2); }
.iam-card[data-g="fred"] .iam-policy-box { border-color: var(--accent3); }
.iam-policy-box .mark { font-weight: 700; }
.iam-policy-box .yes { color: var(--ok); }
.iam-policy-box .no { color: var(--bad); }
.iam-people { display: flex; justify-content: center; gap: 1.2rem; flex-wrap: wrap; margin-top: 0.9rem; }
.iam-person { background: none; border: none; font: inherit; color: var(--ink); cursor: pointer;
display: flex; flex-direction: column; align-items: center; gap: 0.3rem; padding: 0.2rem; }
.iam-person svg { width: 38px; height: 38px; }
.iam-person circle, .iam-person path { stroke: var(--ink-soft); fill: none; stroke-width: 6;
transition: stroke 0.15s; }
.iam-person.is-active circle, .iam-person.is-active path { stroke: var(--accent); }
.iam-card[data-g="ops"] .iam-person.is-active circle,
.iam-card[data-g="ops"] .iam-person.is-active path { stroke: var(--accent2); }
.iam-card[data-g="fred"] .iam-person.is-active circle,
.iam-card[data-g="fred"] .iam-person.is-active path { stroke: var(--accent3); }
.iam-person-name { font-size: 0.8rem; font-weight: 600; }
.iam-answer { margin-top: 1.75rem; text-align: center; font-size: 0.9rem; color: var(--ink-soft);
min-height: 1.3em; padding-top: 1rem; border-top: 1px dashed var(--line); }
.iam-answer b { color: var(--ink); }
@media (prefers-reduced-motion: reduce) {
.infographic-iamsimple * { transition-duration: 0.001ms !important; }
}
</style>
<div class="iam-row">
<div class="iam-card" data-g="dev">
<p class="iam-card-name">👥 Developers</p>
<div class="iam-policy-box">
<span class="mark yes">✓ ✓</span>
<span class="mark no">✕</span>
</div>
<div class="iam-people">
<button class="iam-person" data-name="Alice">
<svg viewBox="0 0 60 60"><circle cx="30" cy="18" r="12"/><path d="M8 54 a22 22 0 0 1 44 0"/></svg>
<span class="iam-person-name">Alice</span>
</button>
<button class="iam-person" data-name="Bob">
<svg viewBox="0 0 60 60"><circle cx="30" cy="18" r="12"/><path d="M8 54 a22 22 0 0 1 44 0"/></svg>
<span class="iam-person-name">Bob</span>
</button>
<button class="iam-person" data-name="Charles">
<svg viewBox="0 0 60 60"><circle cx="30" cy="18" r="12"/><path d="M8 54 a22 22 0 0 1 44 0"/></svg>
<span class="iam-person-name">Charles</span>
</button>
</div>
</div>
<div class="iam-card" data-g="ops">
<p class="iam-card-name">👥 Operations</p>
<div class="iam-policy-box">
<span class="mark yes">✓ ✓</span>
<span class="mark no">✕</span>
</div>
<div class="iam-people">
<button class="iam-person" data-name="David">
<svg viewBox="0 0 60 60"><circle cx="30" cy="18" r="12"/><path d="M8 54 a22 22 0 0 1 44 0"/></svg>
<span class="iam-person-name">David</span>
</button>
<button class="iam-person" data-name="Edward">
<svg viewBox="0 0 60 60"><circle cx="30" cy="18" r="12"/><path d="M8 54 a22 22 0 0 1 44 0"/></svg>
<span class="iam-person-name">Edward</span>
</button>
</div>
</div>
<div class="iam-card" data-g="fred">
<p class="iam-card-name">👤 No group</p>
<div class="iam-policy-box">
<span class="mark yes">✓ ✓</span>
<span class="mark no">✕</span>
</div>
<div class="iam-people">
<button class="iam-person" data-name="Fred">
<svg viewBox="0 0 60 60"><circle cx="30" cy="18" r="12"/><path d="M8 54 a22 22 0 0 1 44 0"/></svg>
<span class="iam-person-name">Fred</span>
</button>
</div>
</div>
</div>
<p class="iam-answer" id="iam-answer">Click a name above.</p>
<script>
(function () {
const wrapper = document.currentScript.closest('.infographic-iamsimple')
|| document.querySelector('.infographic-iamsimple');
const bodyAttr = document.body.getAttribute('data-darkmode');
let dark;
if (bodyAttr === 'true') { dark = true; }
else if (bodyAttr === 'false') { dark = false; }
else {
const match = document.cookie.match(/(?:^|;\s*)darkMode=([^;]+)/);
dark = match ? match[1] === 'true' : false;
}
if (dark) wrapper.setAttribute('data-theme', 'dark');
const answers = {
Alice: 'Alice \u2192 uses the <b>Developers</b> group policy.',
Bob: 'Bob \u2192 uses the <b>Developers</b> group policy.',
Charles: 'Charles \u2192 uses the <b>Developers</b> group policy.',
David: 'David \u2192 uses the <b>Operations</b> group policy.',
Edward: 'Edward \u2192 uses the <b>Operations</b> group policy.',
Fred: 'Fred \u2192 is in no group, so his policy is <b>inline</b> \u2014 attached straight to him.'
};
const answerEl = wrapper.querySelector('#iam-answer');
wrapper.querySelectorAll('.iam-person').forEach(btn => {
btn.addEventListener('click', () => {
wrapper.querySelectorAll('.iam-person').forEach(p => p.classList.remove('is-active'));
btn.classList.add('is-active');
answerEl.innerHTML = answers[btn.dataset.name];
});
});
})();
</script>
</div>.demo {
font-family: system-ui, sans-serif;
padding: 16px;
}
.demo h1 {
color: #2563eb;
margin: 0 0 8px;
}
.demo p {
color: #4b5563;
margin: 0;
}document.querySelector('.demo h1')?.addEventListener('click', () => {
alert('Hello from live preview!');
});🔀 Multiple Group Memberships
Charles is in Developers. David is in Operations. Both are also in Audit Team.
<div class="infographic-iammulti">
<style>
.infographic-iammulti {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f6f4ef;
--accent: #2563eb;
--accent2: #d97706;
--accent3: #16a34a;
--accent4: #7c3aed;
--line: #ddd6c9;
--ok: #1e8e5a;
--bad: #c0392b;
--display-font: Georgia, 'Iowan Old Style', serif;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
color: var(--ink);
background: var(--paper);
padding: 2rem 1.25rem 2.5rem;
max-width: 960px;
margin: 0 auto;
border-radius: 14px;
box-sizing: border-box;
}
.infographic-iammulti[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #121212;
--paper-alt: #1e1e26;
--accent: #6f9bff;
--accent2: #f0a94e;
--accent3: #4fbf87;
--accent4: #b18cf5;
--line: #2c313d;
--ok: #4fbf87;
--bad: #e57373;
}
.infographic-iammulti * { box-sizing: border-box; }
.iam-eyebrow { font-size: 0.75rem; letter-spacing: 0.08em; text-transform: uppercase;
color: var(--ink-soft); margin: 0 0 0.35rem; }
.iam-title { font-family: var(--display-font); font-size: 1.6rem; margin: 0 0 0.4rem; line-height: 1.2; }
.iam-sub { color: var(--ink-soft); font-size: 0.92rem; margin: 0 0 1.75rem; max-width: 62ch; }
.iam-row { display: grid; grid-template-columns: 1fr 1fr 1fr 0.6fr; gap: 1.1rem; }
@media (max-width: 800px) { .iam-row { grid-template-columns: 1fr 1fr; } }
@media (max-width: 500px) { .iam-row { grid-template-columns: 1fr; } }
.iam-card { border: 1.5px solid var(--line); border-radius: 12px; padding: 1.1rem;
background: var(--paper-alt); text-align: center; }
.iam-card[data-g="dev"] { border-top: 4px solid var(--accent); }
.iam-card[data-g="audit"] { border-top: 4px solid var(--accent3); }
.iam-card[data-g="ops"] { border-top: 4px solid var(--accent2); }
.iam-card[data-g="fred"] { border: 1.5px dashed var(--accent4); background: transparent; }
.iam-card-name { font-weight: 700; font-size: 0.95rem; margin: 0 0 0.8rem; }
.iam-card[data-g="dev"] .iam-card-name { color: var(--accent); }
.iam-card[data-g="audit"] .iam-card-name { color: var(--accent3); }
.iam-card[data-g="ops"] .iam-card-name { color: var(--accent2); }
.iam-card[data-g="fred"] .iam-card-name { color: var(--accent4); }
.iam-policy-box { display: inline-flex; flex-direction: column; gap: 0.15rem; border: 1.5px solid;
border-radius: 8px; padding: 0.55rem 0.85rem; background: var(--paper); margin-bottom: 0.8rem; }
.iam-card[data-g="dev"] .iam-policy-box { border-color: var(--accent); }
.iam-card[data-g="audit"] .iam-policy-box { border-color: var(--accent3); }
.iam-card[data-g="ops"] .iam-policy-box { border-color: var(--accent2); }
.iam-card[data-g="fred"] .iam-policy-box { border-color: var(--accent4); }
.iam-policy-box .mark { font-weight: 700; }
.iam-policy-box .yes { color: var(--ok); }
.iam-policy-box .no { color: var(--bad); }
.iam-people { display: flex; justify-content: center; gap: 1rem; flex-wrap: wrap; }
.iam-person { background: none; border: none; font: inherit; color: var(--ink); cursor: pointer;
display: flex; flex-direction: column; align-items: center; gap: 0.3rem; padding: 0.2rem;
position: relative; }
.iam-person svg { width: 36px; height: 36px; }
.iam-person circle, .iam-person path { stroke: var(--ink-soft); fill: none; stroke-width: 6;
transition: stroke 0.15s; }
.iam-person.is-active circle, .iam-person.is-active path { stroke: var(--ink); }
.iam-person.in-dev.is-active circle, .iam-person.in-dev.is-active path { stroke: var(--accent); }
.iam-person-name { font-size: 0.78rem; font-weight: 600; }
.iam-person .badge { font-size: 0.62rem; color: var(--accent3); font-weight: 700; }
.iam-answer { margin-top: 1.75rem; text-align: center; font-size: 0.9rem; color: var(--ink-soft);
min-height: 1.3em; padding-top: 1rem; border-top: 1px dashed var(--line); }
.iam-answer b { color: var(--ink); }
@media (prefers-reduced-motion: reduce) {
.infographic-iammulti * { transition-duration: 0.001ms !important; }
}
</style>
<div class="iam-row">
<div class="iam-card" data-g="dev">
<p class="iam-card-name">👥 Developers</p>
<div class="iam-policy-box">
<span class="mark yes">✓ ✓</span>
<span class="mark no">✕</span>
</div>
<div class="iam-people">
<button class="iam-person" data-name="Alice">
<svg viewBox="0 0 60 60"><circle cx="30" cy="18" r="12"/><path d="M8 54 a22 22 0 0 1 44 0"/></svg>
<span class="iam-person-name">Alice</span>
</button>
<button class="iam-person" data-name="Bob">
<svg viewBox="0 0 60 60"><circle cx="30" cy="18" r="12"/><path d="M8 54 a22 22 0 0 1 44 0"/></svg>
<span class="iam-person-name">Bob</span>
</button>
<button class="iam-person" data-name="Charles">
<svg viewBox="0 0 60 60"><circle cx="30" cy="18" r="12"/><path d="M8 54 a22 22 0 0 1 44 0"/></svg>
<span class="iam-person-name">Charles</span>
<span class="badge">+ Audit</span>
</button>
</div>
</div>
<div class="iam-card" data-g="audit">
<p class="iam-card-name">🛡️ Audit Team</p>
<div class="iam-policy-box">
<span class="mark yes">✓ ✓</span>
<span class="mark no">✕</span>
</div>
<div class="iam-people">
<button class="iam-person" data-name="Charles">
<svg viewBox="0 0 60 60"><circle cx="30" cy="18" r="12"/><path d="M8 54 a22 22 0 0 1 44 0"/></svg>
<span class="iam-person-name">Charles</span>
</button>
<button class="iam-person" data-name="David">
<svg viewBox="0 0 60 60"><circle cx="30" cy="18" r="12"/><path d="M8 54 a22 22 0 0 1 44 0"/></svg>
<span class="iam-person-name">David</span>
</button>
</div>
</div>
<div class="iam-card" data-g="ops">
<p class="iam-card-name">👥 Operations</p>
<div class="iam-policy-box">
<span class="mark yes">✓ ✓</span>
<span class="mark no">✕</span>
</div>
<div class="iam-people">
<button class="iam-person" data-name="David">
<svg viewBox="0 0 60 60"><circle cx="30" cy="18" r="12"/><path d="M8 54 a22 22 0 0 1 44 0"/></svg>
<span class="iam-person-name">David</span>
<span class="badge">+ Audit</span>
</button>
<button class="iam-person" data-name="Edward">
<svg viewBox="0 0 60 60"><circle cx="30" cy="18" r="12"/><path d="M8 54 a22 22 0 0 1 44 0"/></svg>
<span class="iam-person-name">Edward</span>
</button>
</div>
</div>
<div class="iam-card" data-g="fred">
<p class="iam-card-name">👤 No group</p>
<div class="iam-policy-box">
<span class="mark yes">✓ ✓</span>
<span class="mark no">✕</span>
</div>
<div class="iam-people">
<button class="iam-person" data-name="Fred">
<svg viewBox="0 0 60 60"><circle cx="30" cy="18" r="12"/><path d="M8 54 a22 22 0 0 1 44 0"/></svg>
<span class="iam-person-name">Fred</span>
</button>
</div>
</div>
</div>
<p class="iam-answer" id="iam-answer">Click a name above.</p>
<script>
(function () {
const wrapper = document.currentScript.closest('.infographic-iammulti')
|| document.querySelector('.infographic-iammulti');
const bodyAttr = document.body.getAttribute('data-darkmode');
let dark;
if (bodyAttr === 'true') { dark = true; }
else if (bodyAttr === 'false') { dark = false; }
else {
const match = document.cookie.match(/(?:^|;\s*)darkMode=([^;]+)/);
dark = match ? match[1] === 'true' : false;
}
if (dark) wrapper.setAttribute('data-theme', 'dark');
const answers = {
Alice: 'Alice \u2192 <b>Developers</b> policy only.',
Bob: 'Bob \u2192 <b>Developers</b> policy only.',
Charles: 'Charles \u2192 in two groups, so he gets <b>Developers + Audit Team</b> policies combined.',
David: 'David \u2192 in two groups, so he gets <b>Operations + Audit Team</b> policies combined.',
Edward: 'Edward \u2192 <b>Operations</b> policy only.',
Fred: 'Fred \u2192 no group, so his policy is <b>inline</b> \u2014 attached straight to him.'
};
const answerEl = wrapper.querySelector('#iam-answer');
wrapper.querySelectorAll('.iam-person').forEach(btn => {
btn.addEventListener('click', () => {
const name = btn.dataset.name;
wrapper.querySelectorAll('.iam-person').forEach(p =>
p.classList.toggle('is-active', p.dataset.name === name));
answerEl.innerHTML = answers[name];
});
});
})();
</script>
</div>.demo {
font-family: system-ui, sans-serif;
padding: 16px;
}
.demo h1 {
color: #2563eb;
margin: 0 0 8px;
}
.demo p {
color: #4b5563;
margin: 0;
}document.querySelector('.demo h1')?.addEventListener('click', () => {
alert('Hello from live preview!');
});🧩 IAM Policy Structure (JSON Format)
IAM policies are written in JSON.
You will see this structure frequently when:
Creating custom policies
Reading managed policies
Troubleshooting permission issues
Here is an example policy granting access to objects in an S3 bucket:
<div class="infographic-s3policy">
<style>
.infographic-s3policy {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f6f4ef;
--accent: #2563eb;
--accent-soft:#e3ebfd;
--line: #ddd6c9;
--display-font: Georgia, 'Iowan Old Style', serif;
--mono-font: 'SF Mono', Menlo, Consolas, monospace;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
color: var(--ink);
background: var(--paper);
padding: 2rem 1.25rem 2.5rem;
max-width: 980px;
margin: 0 auto;
border-radius: 14px;
box-sizing: border-box;
}
.infographic-s3policy[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #121212;
--paper-alt: #1e1e26;
--accent: #6f9bff;
--accent-soft:#1c2740;
--line: #2c313d;
}
.infographic-s3policy * { box-sizing: border-box; }
.p-eyebrow { font-size: 0.75rem; letter-spacing: 0.08em; text-transform: uppercase;
color: var(--ink-soft); margin: 0 0 0.35rem; }
.p-title { font-family: var(--display-font); font-size: 1.6rem; margin: 0 0 0.4rem; line-height: 1.2; }
.p-sub { color: var(--ink-soft); font-size: 0.92rem; margin: 0 auto 1.75rem; max-width: 65ch; text-align: center; }
.p-anatomy { display: grid; grid-template-columns: 1.15fr 1fr; gap: 1.5rem; align-items: start; }
@media (max-width: 720px) { .p-anatomy { grid-template-columns: 1fr; } }
.p-code { background: var(--paper-alt); border: 1px solid var(--line); border-radius: 10px;
padding: 1.1rem 1.25rem; font-family: var(--mono-font); font-size: 0.82rem; line-height: 1.85;
overflow-x: auto; white-space: pre; }
.p-code .punc { color: var(--ink-soft); }
.p-code .sec { padding: 0.1rem 0.2rem; border-radius: 4px; cursor: pointer; box-decoration-break: clone;
-webkit-box-decoration-break: clone; transition: background 0.15s, box-shadow 0.15s; }
.p-code .sec:hover { background: var(--accent-soft); }
.p-code .sec.is-active { background: var(--accent-soft); box-shadow: 0 0 0 1px var(--accent) inset; }
.p-legend { display: flex; flex-direction: column; gap: 0.5rem; }
.p-legend-item { border: 1px solid var(--line); border-radius: 8px; padding: 0.7rem 0.9rem;
cursor: pointer; background: var(--paper); transition: border-color 0.15s, background 0.15s; }
.p-legend-item:hover { border-color: var(--accent); }
.p-legend-item.is-active { border-color: var(--accent); background: var(--accent-soft); }
.p-legend-item .k { font-weight: 700; font-size: 0.85rem; }
.p-legend-item .v { font-size: 0.8rem; color: var(--ink-soft); margin-top: 0.15rem; }
@media (prefers-reduced-motion: reduce) {
.infographic-s3policy * { transition-duration: 0.001ms !important; }
}
</style>
<p class="p-sub">Click any highlighted section in the JSON — or the matching card — to see what it means in plain English.</p>
<div class="p-anatomy">
<div class="p-code" id="p-code">{
<span class="sec" data-k="version">"Version": "2012-10-17"</span><span class="punc">,</span>
<span class="sec" data-k="id">"Id": "s3-Account-Permissions"</span><span class="punc">,</span>
<span class="punc">"Statement": [</span>
{
<span class="punc">"Sid": "1",</span>
<span class="sec" data-k="effect">"Effect": "Allow"</span><span class="punc">,</span>
<span class="sec" data-k="principal">"Principal": {
"aws": ["arn:aws:iam::123456789012:root"]
}</span><span class="punc">,</span>
<span class="sec" data-k="action">"Action": [
"s3:GetObject",
"s3:PutObject"
]</span><span class="punc">,</span>
<span class="sec" data-k="resource">"Resource": ["arn:aws:s3:::mybucket/*"]</span>
}
<span class="punc">]</span>
}</div>
<div class="p-legend" id="p-legend">
<div class="p-legend-item" data-k="version">
<div class="k">Version</div>
<div class="v">The policy-language version. Always use "2012-10-17" — it's the current syntax.</div>
</div>
<div class="p-legend-item" data-k="id">
<div class="k">Id</div>
<div class="v">An optional name for this whole policy document, just for your own reference.</div>
</div>
<div class="p-legend-item" data-k="effect">
<div class="k">Effect: "Allow"</div>
<div class="v">The statement grants access — the opposite would be "Deny".</div>
</div>
<div class="p-legend-item" data-k="principal">
<div class="k">Principal</div>
<div class="v">Who this applies to: the root user of AWS account 123456789012.</div>
</div>
<div class="p-legend-item" data-k="action">
<div class="k">Action</div>
<div class="v">What they can do: download objects and upload objects.</div>
</div>
<div class="p-legend-item" data-k="resource">
<div class="k">Resource</div>
<div class="v">Where it applies: every object inside "mybucket" (the /* wildcard).</div>
</div>
</div>
</div>
<script>
(function () {
const wrapper = document.currentScript.closest('.infographic-s3policy')
|| document.querySelector('.infographic-s3policy');
const bodyAttr = document.body.getAttribute('data-darkmode');
let dark;
if (bodyAttr === 'true') { dark = true; }
else if (bodyAttr === 'false') { dark = false; }
else {
const match = document.cookie.match(/(?:^|;\s*)darkMode=([^;]+)/);
dark = match ? match[1] === 'true' : false;
}
if (dark) wrapper.setAttribute('data-theme', 'dark');
function setActive(key) {
wrapper.querySelectorAll('.sec').forEach(t => t.classList.toggle('is-active', t.dataset.k === key));
wrapper.querySelectorAll('.p-legend-item').forEach(l => l.classList.toggle('is-active', l.dataset.k === key));
}
wrapper.querySelectorAll('.sec').forEach(t => t.addEventListener('click', () => setActive(t.dataset.k)));
wrapper.querySelectorAll('.p-legend-item').forEach(l => l.addEventListener('click', () => setActive(l.dataset.k)));
})();
</script>
</div>.demo {
font-family: system-ui, sans-serif;
padding: 16px;
}
.demo h1 {
color: #2563eb;
margin: 0 0 8px;
}
.demo p {
color: #4b5563;
margin: 0;
}document.querySelector('.demo h1')?.addEventListener('click', () => {
alert('Hello from live preview!');
});