π IAM Roles
This is the last piece of IAM: Roles.
Why they exist: as you use AWS, you'll spin up services that need to act on your behalf inside your account β reading data, calling other APIs, talking to other services. Just like a person, a service can't do any of that without permission.
What a Role actually is: think of it as a user account, but built for a service instead of a person. It's a set of permissions a service can "assume" when it needs them, rather than something someone logs into directly.
That idea takes a moment to click β so let's walk through an example.
π» Example with EC2 Instance
Letβs say you create an EC2 instance (a virtual server).
Now imagine:
This EC2 instance needs to interact with AWS
For example, accessing another service
By default, The EC2 instance has no permissions
π Connecting EC2 with an IAM Role
To give it permissions:
You create an IAM Role
You assign permissions to that role
You attach the role to the EC2 instance
<div class="infographic-iamrole">
<style>
.infographic-iamrole {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f6f4ef;
--accent: #dc2626; /* role red, matches original */
--accent-soft:#fde3e1;
--accent2: #d97706; /* EC2 orange */
--accent2-soft:#fdecd2;
--line: #ddd6c9;
--ok: #1e8e5a;
--ok-soft: #e2f6ec;
--bad: #c0392b;
--bad-soft: #fbe9e7;
--aws-navy: #232f3e;
--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: 640px;
margin: 0 auto;
border-radius: 14px;
box-sizing: border-box;
}
.infographic-iamrole[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #121212;
--paper-alt: #1e1e26;
--accent: #f36c64;
--accent-soft:#3a2220;
--accent2: #f0a94e;
--accent2-soft:#3a2b12;
--line: #2c313d;
--ok: #4fbf87;
--ok-soft: #143024;
--bad: #e57373;
--bad-soft: #3a2222;
}
.infographic-iamrole * { box-sizing: border-box; }
.ir-eyebrow { font-size: 0.75rem; letter-spacing: 0.08em; text-transform: uppercase;
color: var(--ink-soft); margin: 0 0 0.35rem; text-align: center; }
.ir-title { font-family: var(--display-font); font-size: 1.5rem; margin: 0 0 2rem; line-height: 1.2;
text-align: center; }
.ir-chain { display: flex; flex-direction: column; align-items: center; }
.ir-node { border: 1.5px solid var(--line); border-radius: 12px; padding: 1rem 1.4rem;
display: flex; align-items: center; gap: 0.9rem; background: var(--paper-alt); cursor: pointer;
width: 100%; max-width: 340px; transition: border-color 0.15s, background 0.15s; }
.ir-node:hover { border-color: var(--accent2); }
.ir-node.is-open { background: var(--accent-soft); }
.ir-node[data-n="role"] { border-color: var(--accent); }
.ir-node[data-n="role"].is-open { background: var(--accent-soft); border-color: var(--accent); }
.ir-node[data-n="ec2"] { border-color: var(--accent2); }
.ir-node[data-n="ec2"].is-open { background: var(--accent2-soft); border-color: var(--accent2); }
.ir-icon { font-size: 1.9rem; flex-shrink: 0; }
.ir-node-text .name { font-weight: 700; font-size: 0.95rem; }
.ir-node-text .sub { font-size: 0.78rem; color: var(--ink-soft); }
.ir-detail { display: none; margin-top: 0.6rem; font-size: 0.78rem; color: var(--ink-soft);
border-top: 1px dashed var(--line); padding-top: 0.55rem; width: 100%; max-width: 340px; text-align: center; }
.ir-detail.show { display: block; }
.ir-link-v { width: 1.5px; height: 1.5rem; background: var(--line); margin: 0.3rem 0; }
.ir-link-h-wrap { display: flex; align-items: center; gap: 0.4rem; margin: 0.5rem 0; }
.ir-link-h-wrap .bi { font-size: 1.2rem; color: var(--ink-soft); }
.ir-policy { border: 1.5px solid var(--accent); border-radius: 8px; padding: 0.55rem 0.9rem;
display: inline-flex; flex-direction: column; gap: 0.1rem; background: var(--paper);
cursor: pointer; margin: 0.4rem 0; }
.ir-policy .mark { font-weight: 700; font-family: var(--mono-font); }
.ir-policy .yes { color: var(--ok); }
.ir-policy .no { color: var(--bad); }
.ir-policy-detail { display: none; font-size: 0.78rem; color: var(--ink-soft); text-align: center;
max-width: 300px; margin: -0.2rem 0 0.4rem; }
.ir-policy-detail.show { display: block; }
.ir-aws { width: 130px; height: 68px; border-radius: 8px; background: var(--aws-navy);
display: flex; align-items: center; justify-content: center; cursor: pointer; margin-top: 0.2rem; }
.ir-aws span { color: #fff; font-weight: 800; font-size: 1.5rem; font-family: -apple-system, sans-serif;
letter-spacing: 0.02em; }
.ir-aws-detail { display: none; font-size: 0.78rem; color: var(--ink-soft); text-align: center;
max-width: 320px; margin-top: 0.6rem; }
.ir-aws-detail.show { display: block; }
@media (max-width: 420px) {
.ir-node { padding: 0.85rem 1rem; gap: 0.7rem; }
.ir-icon { font-size: 1.6rem; }
}
@media (prefers-reduced-motion: reduce) {
.infographic-iamrole * { transition-duration: 0.001ms !important; }
}
</style>
<p class="ir-eyebrow">AWS IAM</p>
<h2 class="ir-title">A service needs permissions too</h2>
<div class="ir-chain">
<div class="ir-node" data-n="role">
<span class="ir-icon">βοΈ</span>
<div class="ir-node-text">
<div class="name">IAM Role</div>
<div class="sub">tap to see what it is</div>
</div>
</div>
<div class="ir-detail" data-d="role">A set of permissions β worn by a service, not logged into by a person.</div>
<div class="ir-link-h-wrap"><span class="bi">β</span></div>
<div class="ir-node" data-n="ec2">
<span class="ir-icon">π₯οΈ</span>
<div class="ir-node-text">
<div class="name">EC2 Instance</div>
<div class="sub">virtual server</div>
</div>
</div>
<div class="ir-detail" data-d="ec2">The role is attached to this instance β it "wears" the permissions.</div>
<div class="ir-link-v"></div>
<div style="font-size:0.78rem; color:var(--ink-soft); font-weight:600;">Access AWS</div>
<div class="ir-link-v"></div>
<div class="ir-policy" data-p="policy">
<span class="mark yes">β β</span>
<span class="mark no">β</span>
</div>
<div class="ir-policy-detail" data-pd="policy">Some actions are allowed by the role's policy β others aren't. Same allow/deny logic as any IAM policy.</div>
<div class="ir-link-v"></div>
<div class="ir-aws" data-a="aws">
<span>aws</span>
</div>
<div class="ir-aws-detail" data-ad="aws">The instance calls AWS services using only what the role allows β nothing more.</div>
</div>
<script>
(function () {
const wrapper = document.currentScript.closest('.infographic-iamrole')
|| document.querySelector('.infographic-iamrole');
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');
wrapper.querySelectorAll('.ir-node').forEach(node => {
node.addEventListener('click', () => {
const key = node.dataset.n;
const detail = wrapper.querySelector('.ir-detail[data-d="' + key + '"]');
const wasOpen = node.classList.contains('is-open');
node.classList.toggle('is-open', !wasOpen);
detail.classList.toggle('show', !wasOpen);
});
});
const policy = wrapper.querySelector('.ir-policy');
const policyDetail = wrapper.querySelector('.ir-policy-detail');
policy.addEventListener('click', () => policyDetail.classList.toggle('show'));
const aws = wrapper.querySelector('.ir-aws');
const awsDetail = wrapper.querySelector('.ir-aws-detail');
aws.addEventListener('click', () => awsDetail.classList.toggle('show'));
})();
</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!');
});π Now they work together as one unit
βοΈ How It Works in Practice
Whenever the EC2 instance needs to access AWS resources or make API calls, it goes through its IAM Role to do so.
β Role has the right permissions β access granted
β It doesn't β access denied
<div class="infographic-roleflow">
<style>
.infographic-roleflow {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f6f4ef;
--accent: #dc2626;
--accent-soft:#fde3e1;
--accent2: #d97706;
--accent2-soft:#fdecd2;
--line: #ddd6c9;
--ok: #1e8e5a;
--ok-soft: #e2f6ec;
--bad: #c0392b;
--bad-soft: #fbe9e7;
--aws-navy: #232f3e;
--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: 700px;
margin: 0 auto;
border-radius: 14px;
box-sizing: border-box;
}
.infographic-roleflow[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #121212;
--paper-alt: #1e1e26;
--accent: #f36c64;
--accent-soft:#3a2220;
--accent2: #f0a94e;
--accent2-soft:#3a2b12;
--line: #2c313d;
--ok: #4fbf87;
--ok-soft: #143024;
--bad: #e57373;
--bad-soft: #3a2222;
}
.infographic-roleflow * { box-sizing: border-box; }
.rf-eyebrow { font-size: 0.75rem; letter-spacing: 0.08em; text-transform: uppercase;
color: var(--ink-soft); margin: 0 0 0.35rem; text-align: center; }
.rf-title { font-family: var(--display-font); font-size: 1.5rem; margin: 0 0 1.75rem; line-height: 1.2;
text-align: center; }
.rf-buttons { display: flex; justify-content: center; gap: 0.7rem; margin-bottom: 2.25rem; flex-wrap: wrap; }
.rf-buttons button { font: inherit; font-size: 0.85rem; font-weight: 700; padding: 0.55rem 1.1rem;
border-radius: 999px; cursor: pointer; border: 1.5px solid var(--line); background: var(--paper-alt); color: var(--ink); }
.rf-buttons button:hover { border-color: var(--ink-soft); }
.rf-buttons button.allow { border-color: var(--ok); color: var(--ok); }
.rf-buttons button.deny { border-color: var(--bad); color: var(--bad); }
.rf-track { display: flex; align-items: center; justify-content: center; gap: 0; flex-wrap: nowrap;
position: relative; margin-bottom: 1.5rem; }
@media (max-width: 560px) { .rf-track { flex-direction: column; } }
.rf-node { display: flex; flex-direction: column; align-items: center; gap: 0.4rem; opacity: 0.3;
transition: opacity 0.3s; flex-shrink: 0; }
.rf-node.is-lit { opacity: 1; }
.rf-node .box { width: 62px; height: 62px; border-radius: 12px; border: 1.5px solid var(--line);
display: flex; align-items: center; justify-content: center; font-size: 1.7rem; background: var(--paper-alt);
transition: border-color 0.25s, background 0.25s, transform 0.25s; }
.rf-node.is-lit .box { border-color: var(--accent2); }
.rf-node[data-n="role"].is-lit .box { border-color: var(--accent); }
.rf-node.is-ok .box { border-color: var(--ok); background: var(--ok-soft); }
.rf-node.is-bad .box { border-color: var(--bad); background: var(--bad-soft); animation: node-shake 0.4s; }
.rf-node .lbl { font-size: 0.7rem; font-weight: 700; color: var(--ink-soft); text-align: center; }
@keyframes node-shake {
0%, 100% { transform: translateX(0); }
20% { transform: translateX(-4px); }
40% { transform: translateX(4px); }
60% { transform: translateX(-3px); }
80% { transform: translateX(3px); }
}
.rf-seg { width: 34px; height: 2px; background: var(--line); position: relative; flex-shrink: 0;
transition: background 0.25s; align-self: center; margin-top: -1.1rem; }
.rf-seg.is-lit { background: var(--accent2); }
.rf-seg.is-cut { background: var(--bad); }
@media (max-width: 560px) {
.rf-seg { width: 2px; height: 22px; margin-top: 0; }
}
.rf-result { text-align: center; font-weight: 700; font-size: 0.95rem; min-height: 1.4em; }
.rf-result.ok { color: var(--ok); }
.rf-result.bad { color: var(--bad); }
@media (prefers-reduced-motion: reduce) {
.infographic-roleflow * { animation: none !important; transition-duration: 0.001ms !important; }
}
</style>
<p class="rf-eyebrow">AWS IAM</p>
<h2 class="rf-title">βοΈ Watch a request travel through the role</h2>
<div class="rf-buttons">
<button class="allow" id="rf-play-allow">βΆ Send a request it allows</button>
<button class="deny" id="rf-play-deny">βΆ Send a request it doesn't</button>
</div>
<div class="rf-track" id="rf-track">
<div class="rf-node" data-n="req"><div class="box">π¨</div><span class="lbl">Request</span></div>
<div class="rf-seg" data-seg="1"></div>
<div class="rf-node" data-n="role"><div class="box">βοΈ</div><span class="lbl">Role checks</span></div>
<div class="rf-seg" data-seg="2"></div>
<div class="rf-node" data-n="aws"><div class="box">βοΈ</div><span class="lbl">AWS processes</span></div>
</div>
<p class="rf-result" id="rf-result"> </p>
<script>
(function () {
const wrapper = document.currentScript.closest('.infographic-roleflow')
|| document.querySelector('.infographic-roleflow');
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 nodes = {
req: wrapper.querySelector('.rf-node[data-n="req"]'),
role: wrapper.querySelector('.rf-node[data-n="role"]'),
aws: wrapper.querySelector('.rf-node[data-n="aws"]')
};
const seg1 = wrapper.querySelector('.rf-seg[data-seg="1"]');
const seg2 = wrapper.querySelector('.rf-seg[data-seg="2"]');
const resultEl = wrapper.querySelector('#rf-result');
const btnAllow = wrapper.querySelector('#rf-play-allow');
const btnDeny = wrapper.querySelector('#rf-play-deny');
let running = false;
function reset() {
Object.values(nodes).forEach(n => n.classList.remove('is-lit', 'is-ok', 'is-bad'));
seg1.classList.remove('is-lit', 'is-cut');
seg2.classList.remove('is-lit', 'is-cut');
resultEl.textContent = '\u00a0';
resultEl.classList.remove('ok', 'bad');
}
function play(scenarioAllowed) {
if (running) return;
running = true;
reset();
const steps = [];
steps.push(() => nodes.req.classList.add('is-lit'));
steps.push(() => seg1.classList.add('is-lit'));
steps.push(() => nodes.role.classList.add('is-lit'));
if (scenarioAllowed) {
steps.push(() => seg2.classList.add('is-lit'));
steps.push(() => nodes.aws.classList.add('is-lit', 'is-ok'));
steps.push(() => {
nodes.role.classList.add('is-ok');
resultEl.textContent = 'β Access granted β the role allows this';
resultEl.classList.add('ok');
running = false;
});
} else {
steps.push(() => {
nodes.role.classList.add('is-bad');
seg2.classList.add('is-cut');
});
steps.push(() => {
resultEl.textContent = 'β Access denied β no permission in the role, request stops here';
resultEl.classList.add('bad');
running = false;
});
}
steps.forEach((fn, i) => setTimeout(fn, i * 550));
}
btnAllow.addEventListener('click', () => play(true));
btnDeny.addEventListener('click', () => play(false));
})();
</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!');
});π Common IAM Role Use Cases
IAM Roles are widely used across AWS services.
Some common examples include:
π» EC2 Instance Roles
β‘ Lambda Function Roles
ποΈ CloudFormation Roles
π Any AWS service that needs to act on your behalf will use a role