<?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>Identity Management Archives - Linuxcent</title>
	<atom:link href="https://linuxcent.com/tag/identity-management/feed/" rel="self" type="application/rss+xml" />
	<link>https://linuxcent.com/tag/identity-management/</link>
	<description>Infrastructure security, from the kernel up.</description>
	<lastBuildDate>Tue, 07 Jul 2026 03:03:24 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.0.4</generator>

<image>
	<url>https://linuxcent.com/wp-content/uploads/2026/04/favicon-512x512-1-150x150.png</url>
	<title>Identity Management Archives - Linuxcent</title>
	<link>https://linuxcent.com/tag/identity-management/</link>
	<width>32</width>
	<height>32</height>
</image> 
<site xmlns="com-wordpress:feed-additions:1">211632295</site>	<item>
		<title>The Non-Human Identity Problem Is Back</title>
		<link>https://linuxcent.com/non-human-identity-ai-agents/</link>
					<comments>https://linuxcent.com/non-human-identity-ai-agents/#respond</comments>
		
		<dc:creator><![CDATA[Vamshi Krishna Santhapuri]]></dc:creator>
		<pubDate>Thu, 09 Jul 2026 02:00:00 +0000</pubDate>
				<category><![CDATA[AI Security]]></category>
		<category><![CDATA[AI Agents]]></category>
		<category><![CDATA[Cloud Security]]></category>
		<category><![CDATA[DevSecOps]]></category>
		<category><![CDATA[IAM]]></category>
		<category><![CDATA[Identity Management]]></category>
		<category><![CDATA[LLM Security]]></category>
		<category><![CDATA[Non-Human Identity]]></category>
		<guid isPermaLink="false">https://linuxcent.com/?p=1905</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"> 6</span> <span class="rt-label rt-postfix">minutes</span></span>AI agents are non-human identities — and we are repeating every IAM anti-pattern from the cloud-native era, faster, with actors that are autonomous and manipulable.</p>
<p>The post <a href="https://linuxcent.com/non-human-identity-ai-agents/">The Non-Human Identity Problem Is Back</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"> 6</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>
<p><em>Identity in the Agentic Era, Episode 1</em><br />
<em>Medium | ~2,000 words | 8-minute read</em></p>
<hr />
<p>I was reviewing an AI-powered internal tool a team had shipped to production. It summarized documents, answered questions about internal policy, and could update records in a few internal systems based on what it found.</p>
<p>When I asked what credentials it ran under, the engineer pulled up the service account configuration.</p>
<p><code class="" data-line="">AdministratorAccess</code>.</p>
<p>&#8220;It needed to read from S3, query DynamoDB, call a few internal APIs,&#8221; he said. &#8220;We weren&#8217;t sure exactly what it needed, so we gave it everything and planned to tighten it later.&#8221;</p>
<p>I had heard that sentence before. Almost word for word. In 2017, auditing an AWS account where six Lambda functions each carried three full-access managed policies because someone needed them to work quickly and planned to tighten them later. In 2019, reviewing a GCP project where a service account had <code class="" data-line="">roles/editor</code> at the folder level for the same reason.</p>
<p>We are re-running the same IAM mistakes from the last decade, at speed, with a new class of actors that are harder to audit, harder to predict, and capable of taking autonomous action at a scale no human operator could match.</p>
<p>The non-human identity problem is back. And it brought reinforcements.</p>
<hr />
<h2 id="the-last-time-we-had-this-problem">The Last Time We Had This Problem</h2>
<p>In the early cloud era, the explosion of non-human identities was Lambda functions, EC2 instance profiles, container service accounts, CI/CD pipeline roles. Engineers needed these workloads to access cloud resources. The fastest path was broad permissions. And because nobody was accountable for &#8220;the Lambda&#8217;s IAM role&#8221; specifically, nobody came back to tighten it.</p>
<p>The IAM practices that emerged over the following years — least privilege policies, generated from actual usage rather than estimated requirements; workload identity federation instead of static credentials; OIDC short-lived tokens instead of long-lived access keys — were direct responses to the mess that accumulates when you grant first and audit never.</p>
<p>That took about a decade to normalize. Many environments still aren&#8217;t there.</p>
<p>Now we have AI agents. And we are starting the cycle again from scratch.</p>
<hr />
<h2 id="what-makes-ai-agents-different-as-identities">What Makes AI Agents Different as Identities</h2>
<p>The workload identity problem from 2015 was hard because of scale — hundreds of Lambda functions, thousands of EC2 instances, each needing its own carefully scoped permissions.</p>
<p>AI agents introduce three properties that make the identity problem qualitatively harder.</p>
<p><strong>Autonomy.</strong> A Lambda function does exactly what its code says. An AI agent decides what to do based on a prompt, context, and model behavior. The set of actions it might take is not fully enumerable at deployment time. This means you cannot reason about &#8220;what does this agent need access to&#8221; the same way you reason about a deterministic workload.</p>
<p><strong>Manipulability.</strong> A Lambda function cannot be convinced to do something outside its code by a malicious user prompt. An AI agent can. If the agent has access to customer data and an attacker can inject a prompt that instructs it to exfiltrate that data, the agent&#8217;s valid credentials become the attack vector. This is prompt injection — and it turns IAM from a defense into a liability if permissions are too broad.</p>
<p><strong>Opacity.</strong> When a Lambda function with <code class="" data-line="">s3:GetObject</code> reads a file, you know exactly why: the code called that API. When an AI agent reads a file, the reason is a chain of model decisions that may not be logged, may not be auditable, and may not be consistent across runs. The audit trail that IAM depends on — who accessed what and why — becomes significantly harder to maintain.</p>
<hr />
<h2 id="the-same-mistakes-same-causes">The Same Mistakes, Same Causes</h2>
<p>Walk through an AI agent deployment today and the anti-patterns are familiar:</p>
<p><strong>Over-provisioned service accounts.</strong> The agent needs to read documents, call an API, maybe update a record. Rather than enumerate exactly which documents, which API endpoints, which records — all of which requires upfront work — the team grants broad access and ships. The access never gets tightened because the agent works and nobody is specifically accountable for its permissions.</p>
<p><strong>Static long-lived credentials.</strong> The agent&#8217;s API keys are in environment variables. They were created six months ago. They&#8217;ve never been rotated. If the agent is compromised or its runtime environment is accessed, those credentials are available — and they&#8217;re broad.</p>
<p><strong>No audit trail.</strong> The agent runs under a shared service account used by other services too. When CloudTrail shows an unexpected S3 read from that account, there is no way to know whether it came from the agent, the other service, or something else entirely.</p>
<p><strong>&#8220;We&#8217;ll tighten it later.&#8221;</strong> The phrase that has followed every IAM explosion since 2012. Later rarely comes while the system is working.</p>
<p>These are not AI-specific failures. They are IAM failures that AI deployments are inheriting because the teams building agents are not always the same teams who spent the last decade cleaning up cloud IAM.</p>
<hr />
<h2 id="what-least-privilege-looks-like-for-an-ai-agent">What Least Privilege Looks Like for an AI Agent</h2>
<p>Applying least privilege to an AI agent requires working backwards from what the agent is actually allowed to do, not what it might conceivably need.</p>
<p><strong>Enumerate the agent&#8217;s actions, not its access.</strong> A document summarization agent needs to read specific document stores, nothing else. An agent that updates records needs write access to specific tables with specific conditions — not the whole database. Define the scope from the action, not from the model&#8217;s capability.</p>
<p><strong>Scope by data sensitivity.</strong> Not all data the agent could access is data the agent should access. An agent answering internal HR policy questions does not need read access to financial records. Separate the data stores. Separate the service accounts. The blast radius of a prompt injection attack is bounded by the permissions of the compromised service account.</p>
<p><strong>Use short-lived credentials.</strong> If your AI agent runtime supports OIDC or workload identity federation — and most production platforms now do — use it. The agent gets a short-lived token scoped to its task. No long-lived key to rotate, no orphaned credential to discover later.</p>
<p><strong>One service account per agent, per environment.</strong> Not a shared service account. Not the same account in staging and production. Each agent identity should be independently auditable, independently revocable.</p>
<pre><code class="" data-line=""># What you want to see in CloudTrail
eventSource: s3.amazonaws.com
eventName: GetObject
userIdentity:
  type: AssumedRole
  arn: arn:aws:sts::123456789:assumed-role/agent-doc-summarizer-prod/session

# What you don&#039;t want to see
userIdentity:
  arn: arn:aws:iam::123456789:user/ai-service-shared
</code></pre>
<p>The first entry tells you which agent, which role, which session. The second tells you nothing useful.</p>
<hr />
<h2 id="the-audit-gap">The Audit Gap</h2>
<p>Here is the problem that doesn&#8217;t have a clean solution yet: even with a properly scoped service account, you know <em>that</em> the agent accessed a resource. You do not know <em>why</em> — what prompt triggered it, what reasoning led to it, what the agent was trying to accomplish.</p>
<p>This is the provenance gap in AI systems. Traditional IAM audit logs capture the action and the identity. For AI agents, you need a third dimension: the reasoning chain that produced the action.</p>
<p>Without that, your audit trail for compliance purposes is incomplete. You can prove that <code class="" data-line="">agent-doc-summarizer-prod</code> read a file. You cannot prove whether it did so because a user asked a legitimate question or because an attacker injected a prompt that caused it to retrieve and expose that file.</p>
<p>Solving this requires logging not just the API call, but the context that produced it — the prompt, the model&#8217;s decision path, the tool call sequence. That logging infrastructure doesn&#8217;t exist out of the box in most AI frameworks today. Building it is one of the open problems in AI security, and it is an IAM problem at its core.</p>
<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>Non-human identity lifecycle for AI agents</td>
</tr>
<tr>
<td>CISSP</td>
<td>Domain 3 — Security Architecture</td>
<td>Scoping agent permissions from action definitions</td>
</tr>
<tr>
<td>ISO 27001:2022</td>
<td>5.15 Access control</td>
<td>Least privilege applied to AI workload identities</td>
</tr>
<tr>
<td>ISO 27001:2022</td>
<td>5.18 Access rights</td>
<td>One service account per agent; revocability requirements</td>
</tr>
<tr>
<td>ISO 42001:2023</td>
<td>6.1 AI risk assessment</td>
<td>Identity and access risks specific to AI systems</td>
</tr>
<tr>
<td>NIST AI RMF</td>
<td>GOVERN 1.2</td>
<td>Accountability structures for AI agent actions</td>
</tr>
<tr>
<td>SOC 2</td>
<td>CC6.1 Logical access controls</td>
<td>Service account scoping for AI workloads</td>
</tr>
<tr>
<td>SOC 2</td>
<td>CC7.2 Anomaly detection</td>
<td>Auditing unexpected access patterns from AI identities</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="key-takeaways">Key Takeaways</h2>
<ul>
<li>AI agents are non-human identities. They inherit every IAM anti-pattern we spent a decade fixing for Lambda functions and EC2 instances — and introduce new ones unique to autonomous, manipulable systems</li>
<li>Least privilege for AI agents works backwards from the agent&#8217;s defined actions, not from what it might conceivably need</li>
<li>Prompt injection turns over-permissioned credentials into an attack vector — the agent&#8217;s valid access becomes the attacker&#8217;s access</li>
<li>One service account per agent, per environment. Short-lived credentials where possible. No shared accounts that obscure audit trails</li>
<li>The provenance gap — knowing why an AI agent took an action, not just that it did — is an open problem that traditional IAM logging doesn&#8217;t solve</li>
</ul>
<h2 id="whats-next">What&#8217;s Next</h2>
<p>In EP02, I&#8217;ll cover the specific IAM boundary that most AI pipelines are missing entirely: the data access layer for RAG systems. When your LLM retrieves context from a vector database, what controls what it can retrieve? The answer — for most teams right now — is nothing. And that&#8217;s a problem that has a concrete fix.</p>
<p><a class="a2a_button_mastodon" href="https://www.addtoany.com/add_to/mastodon?linkurl=https%3A%2F%2Flinuxcent.com%2Fnon-human-identity-ai-agents%2F&amp;linkname=The%20Non-Human%20Identity%20Problem%20Is%20Back" 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%2Fnon-human-identity-ai-agents%2F&amp;linkname=The%20Non-Human%20Identity%20Problem%20Is%20Back" 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%2Fnon-human-identity-ai-agents%2F&amp;linkname=The%20Non-Human%20Identity%20Problem%20Is%20Back" 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%2Fnon-human-identity-ai-agents%2F&amp;linkname=The%20Non-Human%20Identity%20Problem%20Is%20Back" 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%2Fnon-human-identity-ai-agents%2F&amp;linkname=The%20Non-Human%20Identity%20Problem%20Is%20Back" 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%2Fnon-human-identity-ai-agents%2F&amp;linkname=The%20Non-Human%20Identity%20Problem%20Is%20Back" 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%2Fnon-human-identity-ai-agents%2F&amp;linkname=The%20Non-Human%20Identity%20Problem%20Is%20Back" 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%2Fnon-human-identity-ai-agents%2F&#038;title=The%20Non-Human%20Identity%20Problem%20Is%20Back" data-a2a-url="https://linuxcent.com/non-human-identity-ai-agents/" data-a2a-title="The Non-Human Identity Problem Is Back"></a></p><p>The post <a href="https://linuxcent.com/non-human-identity-ai-agents/">The Non-Human Identity Problem Is Back</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://linuxcent.com/non-human-identity-ai-agents/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1905</post-id>	</item>
		<item>
		<title>Zero Trust Identity: SPIFFE, SPIRE, mTLS, and Continuous Verification</title>
		<link>https://linuxcent.com/zero-trust-identity-spiffe-spire/</link>
					<comments>https://linuxcent.com/zero-trust-identity-spiffe-spire/#respond</comments>
		
		<dc:creator><![CDATA[Vamshi Krishna Santhapuri]]></dc:creator>
		<pubDate>Sat, 09 May 2026 05:00:00 +0000</pubDate>
				<category><![CDATA[Identity & Authentication]]></category>
		<category><![CDATA[Cloud Security]]></category>
		<category><![CDATA[Identity Management]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[mTLS]]></category>
		<category><![CDATA[SPIFFE]]></category>
		<category><![CDATA[SPIRE]]></category>
		<category><![CDATA[Zero Trust]]></category>
		<guid isPermaLink="false">https://linuxcent.com/?p=1808</guid>

					<description><![CDATA[<p><span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 7</span> <span class="rt-label rt-postfix">minutes</span></span>Zero Trust identity: SPIFFE workload identity, SPIRE attestation, mTLS service mesh, PAM tools, and continuous verification — from /etc/passwd to cryptographic trust.</p>
<p>The post <a href="https://linuxcent.com/zero-trust-identity-spiffe-spire/">Zero Trust Identity: SPIFFE, SPIRE, mTLS, and Continuous Verification</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></description>
										<content:encoded><![CDATA[<span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 7</span> <span class="rt-label rt-postfix">minutes</span></span><style>
pre{position:relative;background:#1e1e1e;color:#d4d4d4;
    padding:16px 16px 16px 20px;border-radius:6px;overflow-x:auto;
    font-family:'JetBrains Mono','Fira Code','Cascadia Code',Consolas,'Courier New',monospace;
    font-size:.88em;line-height:1.6;border-left:4px solid #555}
code{background:#f4f4f4;padding:2px 5px;border-radius:3px;font-size:.9em}
pre code{background:transparent;padding:0;color:inherit}
pre[data-lang="bash"],pre[data-lang="sh"],
pre[data-lang="shell"],pre[data-lang="zsh"]{border-left-color:#4ec9b0}
pre[data-lang="yaml"],pre[data-lang="json"],
pre[data-lang="toml"],pre[data-lang="xml"]{border-left-color:#569cd6}
pre[data-lang="python"],pre[data-lang="go"],pre[data-lang="rust"],
pre[data-lang="java"],pre[data-lang="c"],pre[data-lang="cpp"]{border-left-color:#c586c0}
pre[data-lang="text"],pre[data-lang="output"],
pre[data-lang="console"]{border-left-color:#888}
.lc-copy-btn{position:absolute;top:8px;right:8px;background:#2d2d2d;color:#ccc;
    border:1px solid #444;border-radius:4px;padding:3px 9px;font-size:.75em;
    font-family:system-ui,sans-serif;cursor:pointer;opacity:0;
    transition:opacity .15s,background .15s;line-height:1.6}
pre:hover .lc-copy-btn{opacity:1}
.lc-copy-btn:hover{background:#3a3a3a;color:#fff}
.lc-copy-btn.copied{color:#4ec9b0;border-color:#4ec9b0}
.lc-lang-badge{position:absolute;top:8px;left:20px;font-family:system-ui,sans-serif;
    font-size:.7em;color:#666;text-transform:uppercase;letter-spacing:.04em;
    line-height:1;pointer-events:none;opacity:0;transition:opacity .15s}
pre:hover .lc-lang-badge{opacity:1}
table{border-collapse:collapse;width:100%;margin:16px 0}
th,td{border:1px solid #ddd;padding:10px 14px;text-align:left}
th{background:#f0f0f0;font-weight:600}
tr:nth-child(even){background:#fafafa}
</style>
<p><script>
(function(){
  if(window.__lcCodeEnhanced)return;
  window.__lcCodeEnhanced=true;
  function enhance(){
    document.querySelectorAll('pre').forEach(function(pre){
      var code=pre.querySelector('code');
      var lang='';
      if(code){var m=(code.className||'').match(/language-(\S+)/);if(m)lang=m[1].toLowerCase();}
      if(lang)pre.setAttribute('data-lang',lang);
      if(lang){var badge=document.createElement('span');badge.className='lc-lang-badge';badge.textContent=lang;pre.insertBefore(badge,pre.firstChild);}
      var btn=document.createElement('button');
      btn.className='lc-copy-btn';btn.textContent='Copy';btn.setAttribute('aria-label','Copy code to clipboard');
      pre.appendChild(btn);
      btn.addEventListener('click',function(){
        var text=code?code.innerText:pre.innerText;
        if(navigator.clipboard&&window.isSecureContext){
          navigator.clipboard.writeText(text).then(function(){ok(btn);}).catch(function(){fb(text,btn);});
        }else{fb(text,btn);}
      });
    });
  }
  function ok(btn){btn.textContent='Copied!';btn.classList.add('copied');setTimeout(function(){btn.textContent='Copy';btn.classList.remove('copied');},2000);}
  function fb(text,btn){
    try{var ta=document.createElement('textarea');ta.value=text;ta.style.cssText='position:fixed;left:-9999px;top:-9999px;opacity:0';document.body.appendChild(ta);ta.select();document.execCommand('copy');document.body.removeChild(ta);ok(btn);}
    catch(e){btn.textContent='✗ Failed';setTimeout(function(){btn.textContent='Copy';},2000);}
  }
  if(document.readyState==='loading'){document.addEventListener('DOMContentLoaded',enhance);}else{enhance();}
})();
</script></p>
<p><em>The Identity Stack, Episode 13</em><br />
<a href="/entra-id-linux-login/">EP12: Entra ID + Linux</a> → <strong>EP13</strong></p>
<hr />
<h2 id="tldr">TL;DR</h2>
<ul>
<li>Zero Trust means &#8220;never trust, always verify&#8221; — identity is verified continuously, not just at login time; network location provides no implicit trust</li>
<li>Human identity (users) and workload identity (services, pods, jobs) are separate problems — LDAP/Kerberos/OIDC solve the human side; SPIFFE/SPIRE solve the workload side</li>
<li>SPIFFE (Secure Production Identity Framework For Everyone) defines a standard for workload identity — a SPIFFE ID is a URI like <code class="" data-line="">spiffe://corp.com/ns/prod/sa/payments-svc</code></li>
<li>SPIRE (SPIFFE Runtime Environment) issues short-lived X.509 SVIDs (SPIFFE Verifiable Identity Documents) to workloads — certificates that rotate automatically, every hour</li>
<li>mTLS (mutual TLS) is how workloads prove identity to each other — both sides present certificates; no passwords, no API keys</li>
<li>The evolution: <code class="" data-line="">/etc/passwd</code> (1970) → NIS → LDAP → Kerberos → SAML → OIDC → SPIFFE/SPIRE — the problem has always been the same; the trust boundary keeps moving outward</li>
</ul>
<hr />
<h2 id="the-big-picture-from-etcpasswd-to-zero-trust">The Big Picture: From /etc/passwd to Zero Trust</h2>
<pre><code class="" data-line="">1970s  /etc/passwd              ← trust: the local machine
       One machine, one user list

1984   NIS / Yellow Pages       ← trust: the local network
       Centralized, but cleartext, flat

1993   LDAP                     ← trust: the directory server
       Hierarchical, scalable, encrypted (eventually)

1988   Kerberos                 ← trust: the KDC
       Tickets instead of passwords, network-wide

2002   SAML                     ← trust: the IdP assertion
       Identity crosses the internet

2014   OIDC / OAuth2            ← trust: the JWT signature
       API-native, mobile-native, developer-native

2017   SPIFFE / SPIRE           ← trust: the workload certificate
       Automated identity for services, not humans

2026   Zero Trust               ← trust: nothing, verify everything
       Continuous verification, short-lived credentials,
       device posture, behavioral signals
</code></pre>
<p>EP01 of this series started with the chaos of per-machine <code class="" data-line="">/etc/passwd</code>. This episode — EP13 — closes the loop: from that chaos to a model where identity is verified continuously, credentials expire in hours not years, and the network provides no implicit trust.</p>
<hr />
<h2 id="the-assumption-that-zero-trust-rejects">The Assumption That Zero Trust Rejects</h2>
<p>Traditional security assumed: if you&#8217;re on the internal network, you&#8217;re trusted. A VPN user was treated as equivalent to someone at a desk in the office. A service running on the same Kubernetes node as another service was implicitly trusted.</p>
<p>That assumption broke in practice:</p>
<ul>
<li>Compromised VPN credentials gave attackers full internal access</li>
<li>Lateral movement after initial compromise was easy — once inside, everything trusted you</li>
<li>Perimeter-based security had no visibility into east-west traffic (service-to-service)</li>
</ul>
<p>Zero Trust inverts the model: <strong>the network provides no trust.</strong> Every access request is verified — user or service, internal or external, first request or hundredth. Trust is dynamic, contextual, and short-lived.</p>
<hr />
<h2 id="human-zero-trust-continuous-verification">Human Zero Trust: Continuous Verification</h2>
<p>For human users, Zero Trust extends OIDC and Conditional Access:</p>
<p><strong>Short-lived tokens.</strong> Access tokens expire in 1 hour (OIDC standard). Refresh tokens are revocable. A user who is terminated can have their refresh tokens revoked in Entra ID — the next time their app tries to use the refresh token, it fails. The maximum blast radius of a stolen token is bounded by its lifetime.</p>
<p><strong>Device posture.</strong> The device the user authenticates from is part of the identity assertion. Conditional Access can require: device is managed (Intune-enrolled), device is compliant (no malware, full-disk encryption enabled, OS patched). A valid user credential from an unmanaged device is denied.</p>
<p><strong>Behavioral signals.</strong> Entra ID Identity Protection and similar systems analyze login patterns — unusual location, impossible travel (login from Mumbai, then New York 5 minutes later), unfamiliar device. High-risk sign-ins trigger step-up authentication or are blocked automatically.</p>
<p><strong>Privileged Access Management (PAM).</strong> For privileged operations (production shell access, AD admin), Zero Trust adds time-bounded just-in-time access:</p>
<pre><code class="" data-line="">Request:  &quot;I need admin access to db01.corp.com for 2 hours to investigate an incident&quot;
Approval: Manager approves via Slack/email/ticketing system
Grant:    Temporary role assignment or password checkout from the PAM vault
Access:   User SSHes with a one-time or time-limited credential
Expire:   Credential automatically revoked after 2 hours
Audit:    Full session recording available for review
</code></pre>
<p>CyberArk, BeyondTrust, and HashiCorp Vault implement this model. Vault&#8217;s SSH Secrets Engine issues short-lived SSH certificates:</p>
<pre><code class="" data-line=""># Request a signed SSH certificate (valid 30 minutes)
vault ssh \
  -role=prod-admin \
  -mode=ca \
  -mount-point=ssh-client-signer \
  vamshi@db01.corp.com

# Vault issues a certificate signed by the server&#039;s trusted CA
# sshd on db01 trusts that CA — no authorized_keys needed
# Certificate expires in 30 minutes — no cleanup required
</code></pre>
<hr />
<h2 id="workload-identity-the-non-human-problem">Workload Identity: The Non-Human Problem</h2>
<p>Services don&#8217;t have passwords they can type. A microservice calling another microservice needs to prove its identity — but you can&#8217;t give a Kubernetes pod a static API key (it&#8217;ll be in a config file, in a git repo, or in a crash dump within 6 months).</p>
<p>Workload identity solves this with <strong>short-lived, automatically rotated certificates</strong> — the service&#8217;s identity is its certificate, issued by a trusted CA, expiring in minutes to hours.</p>
<pre><code class="" data-line="">Traditional:                     Zero Trust:
  payments-svc → orders-svc        payments-svc → orders-svc
  Authentication: API key           Authentication: mTLS (X.509 cert)
  &quot;Bearer sk_live_abc123&quot;           cert: spiffe://corp.com/ns/prod/sa/payments-svc
  Rotation: manual (rarely done)    Rotation: automatic, every hour
  Revocation: change the key        Revocation: cert expires; new cert issued
  Audit: &quot;API key was used&quot;         Audit: &quot;spiffe://payments-svc → spiffe://orders-svc&quot;
</code></pre>
<hr />
<h2 id="spiffe-the-standard">SPIFFE: The Standard</h2>
<p>SPIFFE (Secure Production Identity Framework For Everyone) defines what a workload identity looks like. The core concept is the SPIFFE ID — a URI in the format:</p>
<pre><code class="" data-line="">spiffe://&lt;trust-domain&gt;/&lt;workload-path&gt;

Examples:
  spiffe://corp.com/ns/prod/sa/payments-svc
  spiffe://corp.com/region/us-east/service/auth-api
  spiffe://corp.com/k8s/cluster-prod/namespace/payments/pod/payments-svc-abc123
</code></pre>
<p>The trust domain (<code class="" data-line="">corp.com</code>) is the organizational boundary. The path is the workload identifier — typically encoding namespace, service account, or cluster information.</p>
<p>A SPIFFE ID is embedded in an SVID (SPIFFE Verifiable Identity Document) — either an X.509 certificate (X.509-SVID) or a JWT (JWT-SVID). The X.509-SVID is the standard form: the SPIFFE ID appears in the certificate&#8217;s Subject Alternative Name (SAN) field.</p>
<pre><code class="" data-line="">X.509 Certificate (SVID):
  Subject: CN=payments-svc
  SAN: URI=spiffe://corp.com/ns/prod/sa/payments-svc
  Validity: 1 hour
  Issuer: SPIRE Intermediate CA
  Signed by: corp.com trust bundle
</code></pre>
<p>Any service that has the <code class="" data-line="">corp.com</code> trust bundle (the CA certificate chain) can verify that a certificate with <code class="" data-line="">spiffe://corp.com/...</code> in the SAN was issued by the authorized CA for that trust domain.</p>
<hr />
<h2 id="spire-the-runtime">SPIRE: The Runtime</h2>
<p>SPIRE (SPIFFE Runtime Environment) is the reference implementation that issues SVIDs to workloads.</p>
<pre><code class="" data-line="">SPIRE Server
  ├── Node attestation: verifies the identity of the node/VM
  │   (AWS instance identity document, GCP service account, k8s node SA)
  └── Workload attestation: verifies the identity of the process
      (Kubernetes SA, Unix UID/GID, Docker container labels)
         │
         │ issues X.509 SVIDs (short-lived, auto-rotated)
         ▼
SPIRE Agent (runs on every node)
         │
         │ SPIFFE Workload API (Unix socket)
         ▼
Workload (your service)
  → gets its own certificate
  → gets the trust bundle (CA certs of trusted domains)
  → uses cert for mTLS with other services
</code></pre>
<p>The workload fetches its identity via the Workload API socket — no environment variables, no file mounts. The SPIRE Agent pushes new certificates before the old ones expire. Rotation is transparent to the workload.</p>
<pre><code class="" data-line=""># On a node with SPIRE Agent running:
# Fetch the SVID for the current workload
spire-agent api fetch x509 \
  -socketPath /run/spire/sockets/agent.sock

# Output shows:
# SPIFFE ID: spiffe://corp.com/ns/prod/sa/payments-svc
# Certificate: (PEM)
# Trust bundle: (PEM of issuing CA chain)
# Expires: 2026-04-27T02:00:00Z (1 hour from now)
</code></pre>
<hr />
<h2 id="mtls-both-sides-show-id">mTLS: Both Sides Show ID</h2>
<p>Mutual TLS (mTLS) is what makes SPIFFE useful operationally. In standard TLS, only the server presents a certificate — the client just verifies it. In mTLS, both sides present certificates. Both sides verify the other&#8217;s certificate against the trust bundle.</p>
<pre><code class="" data-line="">payments-svc → orders-svc connection:

TLS handshake:
  payments-svc presents: spiffe://corp.com/ns/prod/sa/payments-svc cert
  orders-svc presents:   spiffe://corp.com/ns/prod/sa/orders-svc cert

  Both verify:
    • cert signed by trusted CA (the corp.com SPIRE CA)
    • cert not expired
    • SPIFFE ID in SAN matches what&#039;s expected

  After handshake: encrypted channel, both sides verified
  Authorization: orders-svc checks its policy:
    &quot;is spiffe://corp.com/ns/prod/sa/payments-svc allowed to call /api/orders?&quot;
</code></pre>
<p>Service meshes (Istio, Linkerd, Consul Connect) implement mTLS transparently — the application doesn&#8217;t handle certificates; the sidecar proxy does. In Istio&#8217;s case, Citadel (now istiod) acts as the SPIFFE-compatible CA, issuing certificates to envoy sidecars. The application code doesn&#8217;t change.</p>
<hr />
<h2 id="open-policy-agent-authorization-after-identity">Open Policy Agent: Authorization After Identity</h2>
<p>Zero Trust separates identity from authorization. Once you know who the caller is (SPIFFE ID, OIDC token, user cert), a policy engine decides what they can do.</p>
<p>OPA (Open Policy Agent) is the standard for this:</p>
<pre><code class="" data-line=""># opa-policy.rego
package authz

# payments-svc can read orders; nothing else can write orders
allow {
  input.caller == &quot;spiffe://corp.com/ns/prod/sa/payments-svc&quot;
  input.method == &quot;GET&quot;
  startswith(input.path, &quot;/api/orders&quot;)
}

default allow = false
</code></pre>
<p>The service checks OPA on each request: &#8220;caller=X wants to do Y to Z — allowed?&#8221; OPA evaluates the policy and returns a decision. The policy is version-controlled, tested, and deployed independently of the service.</p>
<hr />
<h2 id="common-misconceptions"><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;" /> Common Misconceptions</h2>
<p><strong>&#8220;Zero Trust means no trust.&#8221;</strong> Zero Trust means trust is earned dynamically through verification, not granted by network location. A verified user with a valid, compliant device and MFA is trusted — for the scope and duration of the verified session. The &#8220;zero&#8221; refers to implicit trust, not trust itself.</p>
<p><strong>&#8220;SPIFFE replaces OIDC.&#8221;</strong> SPIFFE is for workload (service) identity. OIDC is for human (user) identity. They complement each other — a service has a SPIFFE identity; a user has an OIDC identity; the authorization layer accepts both.</p>
<p><strong>&#8220;mTLS is complex to implement.&#8221;</strong> With a service mesh (Istio, Linkerd), mTLS is transparent — the sidecar handles it. Without a service mesh, the application needs to use the SPIFFE Workload API. The complexity is real but manageable, especially compared to the alternative of static API keys.</p>
<hr />
<h2 id="framework-alignment">Framework Alignment</h2>
<table>
<thead>
<tr>
<th>Domain</th>
<th>Relevance</th>
</tr>
</thead>
<tbody>
<tr>
<td>CISSP Domain 5: Identity and Access Management</td>
<td>Zero Trust extends IAM to workloads (SPIFFE) and continuous verification (short-lived tokens, device posture) — it&#8217;s the current frontier of identity architecture</td>
</tr>
<tr>
<td>CISSP Domain 3: Security Architecture and Engineering</td>
<td>The separation of identity (SPIFFE ID), authentication (mTLS), and authorization (OPA) is a clean architectural decomposition that scales to complex multi-service environments</td>
</tr>
<tr>
<td>CISSP Domain 4: Communications and Network Security</td>
<td>mTLS encrypts and authenticates every service-to-service connection — it eliminates the assumption that east-west traffic on the internal network is safe</td>
</tr>
<tr>
<td>CISSP Domain 1: Security and Risk Management</td>
<td>Zero Trust is a risk management posture — it accepts that perimeter breach is inevitable and limits blast radius through continuous verification and least-privilege</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="key-takeaways">Key Takeaways</h2>
<ul>
<li>Zero Trust rejects network-based implicit trust — every request is verified regardless of source</li>
<li>Human identity: short-lived OIDC tokens, device posture checks, Conditional Access, JIT privileged access (Vault, CyberArk)</li>
<li>Workload identity: SPIFFE IDs in X.509 certificates, issued by SPIRE, rotated automatically every hour — no static API keys</li>
<li>mTLS lets services verify each other&#8217;s identity at the TLS layer — service meshes (Istio, Linkerd) implement it transparently</li>
<li>OPA handles authorization after identity is established — who you are ≠ what you can do</li>
<li>The series arc: <code class="" data-line="">/etc/passwd</code> → NIS → LDAP → Kerberos → SAML → OIDC → SPIFFE/SPIRE — the problem has always been &#8220;how do you know who someone is, at scale, without trusting the network?&#8221; The answer keeps getting better.</li>
</ul>
<hr />
<p>What does identity look like at your organization — still static API keys and shared service accounts, or moving toward SPIFFE and short-lived credentials? <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f447.png" alt="👇" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>
<hr />
<p><em>The Identity Stack: From LDAP to Zero Trust — 13 episodes complete.</em></p>
<p><a href="/what-is-ldap/">Start from EP01: What Is LDAP →</a></p>
<p><a class="a2a_button_mastodon" href="https://www.addtoany.com/add_to/mastodon?linkurl=https%3A%2F%2Flinuxcent.com%2Fzero-trust-identity-spiffe-spire%2F&amp;linkname=Zero%20Trust%20Identity%3A%20SPIFFE%2C%20SPIRE%2C%20mTLS%2C%20and%20Continuous%20Verification" 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%2Fzero-trust-identity-spiffe-spire%2F&amp;linkname=Zero%20Trust%20Identity%3A%20SPIFFE%2C%20SPIRE%2C%20mTLS%2C%20and%20Continuous%20Verification" 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%2Fzero-trust-identity-spiffe-spire%2F&amp;linkname=Zero%20Trust%20Identity%3A%20SPIFFE%2C%20SPIRE%2C%20mTLS%2C%20and%20Continuous%20Verification" 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%2Fzero-trust-identity-spiffe-spire%2F&amp;linkname=Zero%20Trust%20Identity%3A%20SPIFFE%2C%20SPIRE%2C%20mTLS%2C%20and%20Continuous%20Verification" 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%2Fzero-trust-identity-spiffe-spire%2F&amp;linkname=Zero%20Trust%20Identity%3A%20SPIFFE%2C%20SPIRE%2C%20mTLS%2C%20and%20Continuous%20Verification" 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%2Fzero-trust-identity-spiffe-spire%2F&amp;linkname=Zero%20Trust%20Identity%3A%20SPIFFE%2C%20SPIRE%2C%20mTLS%2C%20and%20Continuous%20Verification" 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%2Fzero-trust-identity-spiffe-spire%2F&amp;linkname=Zero%20Trust%20Identity%3A%20SPIFFE%2C%20SPIRE%2C%20mTLS%2C%20and%20Continuous%20Verification" 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%2Fzero-trust-identity-spiffe-spire%2F&#038;title=Zero%20Trust%20Identity%3A%20SPIFFE%2C%20SPIRE%2C%20mTLS%2C%20and%20Continuous%20Verification" data-a2a-url="https://linuxcent.com/zero-trust-identity-spiffe-spire/" data-a2a-title="Zero Trust Identity: SPIFFE, SPIRE, mTLS, and Continuous Verification"></a></p><p>The post <a href="https://linuxcent.com/zero-trust-identity-spiffe-spire/">Zero Trust Identity: SPIFFE, SPIRE, mTLS, and Continuous Verification</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://linuxcent.com/zero-trust-identity-spiffe-spire/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1808</post-id>	</item>
		<item>
		<title>Entra ID Linux Login: SSH Authentication with Azure AD Credentials</title>
		<link>https://linuxcent.com/entra-id-linux-login/</link>
					<comments>https://linuxcent.com/entra-id-linux-login/#respond</comments>
		
		<dc:creator><![CDATA[Vamshi Krishna Santhapuri]]></dc:creator>
		<pubDate>Sat, 09 May 2026 02:00:00 +0000</pubDate>
				<category><![CDATA[Identity & Authentication]]></category>
		<category><![CDATA[Authentication]]></category>
		<category><![CDATA[Azure AD]]></category>
		<category><![CDATA[Cloud Security]]></category>
		<category><![CDATA[Entra ID]]></category>
		<category><![CDATA[Identity Management]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[SSH]]></category>
		<guid isPermaLink="false">https://linuxcent.com/?p=1805</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"> 6</span> <span class="rt-label rt-postfix">minutes</span></span>Enable Entra ID SSH login on Linux: aad-auth package, Conditional Access Policies, pam_aad stack, and Entra ID Connect for hybrid on-prem sync.</p>
<p>The post <a href="https://linuxcent.com/entra-id-linux-login/">Entra ID Linux Login: SSH Authentication with Azure AD Credentials</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"> 6</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>
<p><em>The Identity Stack, Episode 12</em><br />
<a href="/identity-providers-explained/">EP11: Identity Providers</a> → <strong>EP12</strong> → <a href="/zero-trust-identity-spiffe-spire/">EP13: Zero Trust Identity</a> → &#8230;</p>
<hr />
<h2 id="tldr">TL;DR</h2>
<ul>
<li>Entra ID (Azure AD) Linux login lets you SSH into a VM using your Azure AD credentials — no local Linux accounts, no SSH keys to distribute</li>
<li>The stack: <code class="" data-line="">aad-auth</code> package + <code class="" data-line="">pam_aad.so</code> + SSSD — Azure authenticates via OIDC device code flow or password, then maps the identity to a local Linux UID</li>
<li>Entra ID is not AD — it&#8217;s OIDC/OAuth2 native, with no LDAP and no Kerberos (unless you add Azure AD DS, a separate managed service)</li>
<li>Conditional Access Policies can gate Linux logins — MFA, device compliance, location restrictions — the same policies as for web apps</li>
<li>Two login modes: interactive (browser-based device code, for non-Azure VMs) and integrated (Azure IMDS-based, for Azure VMs)</li>
<li>Required roles: <code class="" data-line="">Virtual Machine Administrator Login</code> or <code class="" data-line="">Virtual Machine User Login</code> on the VM — IAM, not local sudoers</li>
</ul>
<hr />
<h2 id="the-big-picture-how-entra-id-linux-login-works">The Big Picture: How Entra ID Linux Login Works</h2>
<pre><code class="" data-line="">User: ssh vamshi@vm.corp.azure.com

  sshd on Linux VM
      │
      ▼
  PAM (/etc/pam.d/sshd)
      │
      ├── pam_aad.so (auth)
      │     │
      │     │  OIDC device code flow:
      │     │  &quot;Go to microsoft.com/devicelogin and enter code ABCD-1234&quot;
      │     │  User authenticates in browser with MFA
      │     │  Entra ID issues id_token + access_token
      │     ▼
      │   pam_aad validates token:
      │     • signature (JWKS from Entra ID)
      │     • tenant ID (iss claim)
      │     • VM resource audience (aud claim)
      │     • group membership (groups claim)
      │
      └── pam_mkhomedir (session)
            Creates /home/vamshi@corp.com on first login

  Shell session created
  whoami → vamshi_corp_com (sanitized UPN for Linux username)
</code></pre>
<p>EP11 mapped the IdP landscape. This episode gets specific: Entra ID and Linux. Understanding this matters because Entra ID is increasingly where enterprise identities live, and cloud VMs that SSH into with local accounts are an operational and security liability.</p>
<hr />
<h2 id="entra-id-vs-active-directory-whats-different">Entra ID vs Active Directory: What&#8217;s Different</h2>
<p>This distinction matters before configuring anything.</p>
<table>
<thead>
<tr>
<th></th>
<th>Active Directory (on-prem)</th>
<th>Entra ID (cloud)</th>
</tr>
</thead>
<tbody>
<tr>
<td>Protocol</td>
<td>LDAP + Kerberos</td>
<td>OIDC + OAuth2</td>
</tr>
<tr>
<td>Directory queries</td>
<td><code class="" data-line="">ldapsearch</code></td>
<td>Microsoft Graph API</td>
</tr>
<tr>
<td>Linux join</td>
<td><code class="" data-line="">realm join</code> (adcli + SSSD)</td>
<td><code class="" data-line="">aad-auth</code> package</td>
</tr>
<tr>
<td>Authentication</td>
<td>Kerberos tickets</td>
<td>JWT tokens</td>
</tr>
<tr>
<td>Group policy</td>
<td>GPO via Sysvol</td>
<td>Conditional Access + Intune</td>
</tr>
<tr>
<td>Network requirement</td>
<td>DC reachable on LAN/VPN</td>
<td>HTTPS to login.microsoftonline.com</td>
</tr>
</tbody>
</table>
<p>Entra ID has no LDAP interface and no Kerberos realm. You cannot run <code class="" data-line="">ldapsearch</code> against it. You cannot <code class="" data-line="">kinit</code> to it. The authentication protocol is entirely OIDC/OAuth2 — the same protocol your browser uses to &#8220;Login with Microsoft.&#8221;</p>
<p>If you need LDAP and Kerberos from Azure, that&#8217;s <strong>Azure AD Domain Services</strong> — a separate managed service that Microsoft runs, which does speak LDAP and Kerberos. It&#8217;s not Entra ID; it&#8217;s a managed AD replica in Azure. EP12 covers the Entra ID path — the modern, protocol-native approach.</p>
<hr />
<h2 id="prerequisites">Prerequisites</h2>
<pre><code class="" data-line=""># Azure side:
# 1. The VM&#039;s managed identity must be enabled (System-assigned)
# 2. Two Entra ID roles assigned on the VM resource:
#    - &quot;Virtual Machine Administrator Login&quot; (for sudo access)
#    - &quot;Virtual Machine User Login&quot; (for regular access)
# 3. Conditional Access policies that apply to the VM login scope

# VM side (Ubuntu 20.04+ / RHEL 8+):
# Install the aad-auth package (Microsoft-maintained)
curl -sSL https://packages.microsoft.com/keys/microsoft.asc \
  | gpg --dearmor -o /usr/share/keyrings/microsoft.gpg
echo &quot;deb [signed-by=/usr/share/keyrings/microsoft.gpg] \
  https://packages.microsoft.com/ubuntu/22.04/prod jammy main&quot; \
  &gt; /etc/apt/sources.list.d/microsoft.list
apt-get update &amp;&amp; apt-get install -y aad-auth
</code></pre>
<hr />
<h2 id="configuration">Configuration</h2>
<pre><code class="" data-line=""># Configure the aad-auth package
aad-auth configure \
  --tenant-id 12345678-1234-1234-1234-123456789abc \
  --app-id 87654321-4321-4321-4321-cba987654321

# This writes /etc/aad.conf:
# [aad]
# tenant_id = 12345678-...
# app_id = 87654321-...
# version = 1

# Verify the PAM configuration was updated
grep pam_aad /etc/pam.d/common-auth
# auth [success=1 default=ignore] pam_aad.so
</code></pre>
<p>The <code class="" data-line="">aad-auth</code> package installs <code class="" data-line="">pam_aad.so</code> and configures PAM automatically. It also modifies <code class="" data-line="">/etc/nsswitch.conf</code> to add <code class="" data-line="">aad</code> as a source for <code class="" data-line="">passwd</code> lookups — so <code class="" data-line="">getent passwd vamshi@corp.com</code> works after the first login.</p>
<hr />
<h2 id="the-login-flow">The Login Flow</h2>
<h3 id="on-an-azure-vm-integrated-mode">On an Azure VM (Integrated mode)</h3>
<p>Azure VMs have access to the Instance Metadata Service (IMDS) at <code class="" data-line="">169.254.169.254</code>. <code class="" data-line="">pam_aad</code> uses the VM&#8217;s managed identity to get a token from IMDS, which proves the VM is trusted, then validates the user&#8217;s token against the tenant.</p>
<pre><code class="" data-line=""># User SSHes with username as UPN (user@tenant.onmicrosoft.com or user@corp.com)
ssh vamshi@corp.com@vm.eastus.cloudapp.azure.com

# Or use the short form if the tenant is configured:
ssh vamshi@corp.com@vm.eastus.cloudapp.azure.com
</code></pre>
<p>On first connection, <code class="" data-line="">pam_aad</code> initiates the device code flow:</p>
<pre><code class="" data-line="">To sign in, use a web browser to open https://microsoft.com/devicelogin
and enter the code ABCD-1234 to authenticate.
</code></pre>
<p>The user opens the URL in any browser (on any device), enters the code, and authenticates with their Entra ID credentials + MFA. The SSH session gets a token. Subsequent logins within the token cache TTL skip the device code step.</p>
<h3 id="username-format-on-the-linux-system">Username format on the Linux system</h3>
<p>Entra ID usernames (UPNs) contain <code class="" data-line="">@</code> — not valid in Linux usernames. <code class="" data-line="">aad-auth</code> sanitizes the UPN:</p>
<pre><code class="" data-line="">vamshi@corp.com → vamshi_corp_com    (default)
# or, with shorter_username enabled in /etc/aad.conf:
vamshi@corp.com → vamshi
</code></pre>
<p>The UID is derived from the Azure AD Object ID (a deterministic hash) — stable across logins, same UID on every VM in the tenant.</p>
<hr />
<h2 id="conditional-access-for-linux-logins">Conditional Access for Linux Logins</h2>
<p>Conditional Access Policies in Entra ID apply to Linux VM logins the same way they apply to web app logins.</p>
<pre><code class="" data-line="">Policy: Require MFA for Linux VM Login
  Conditions:
    Cloud apps: &quot;Azure Linux Virtual Machine Sign-In&quot;
    Users: All users (or specific groups)
  Grant:
    Require multi-factor authentication
    Require compliant device (optional)
</code></pre>
<p>With this policy, every SSH login triggers MFA — regardless of whether the client machine supports it. The MFA challenge appears in the device code flow (the browser window the user opens).</p>
<p>You can also enforce:<br />
&#8211; <strong>Location restrictions</strong> — only from corporate IP ranges<br />
&#8211; <strong>Device compliance</strong> — device must be Intune-managed<br />
&#8211; <strong>Sign-in risk</strong> — block logins flagged as risky by Entra ID Identity Protection</p>
<p>This is the operational shift: Linux login security is now managed in the same Conditional Access policy engine as every other Entra ID-protected resource. No more per-machine PAM configuration for MFA.</p>
<hr />
<h2 id="role-based-access-who-can-log-in">Role-Based Access: Who Can Log In</h2>
<p>Access to the VM is controlled by Azure RBAC — not by local Linux groups or sudoers.</p>
<pre><code class="" data-line=""># Grant a user SSH access to the VM
az role assignment create \
  --assignee vamshi@corp.com \
  --role &quot;Virtual Machine User Login&quot; \
  --scope /subscriptions/SUB_ID/resourceGroups/RG/providers/Microsoft.Compute/virtualMachines/VM_NAME

# Grant admin (sudo) access
az role assignment create \
  --assignee vamshi@corp.com \
  --role &quot;Virtual Machine Administrator Login&quot; \
  --scope /subscriptions/SUB_ID/...
</code></pre>
<p><code class="" data-line="">Virtual Machine Administrator Login</code> maps to the <code class="" data-line="">sudo</code> group on the Linux VM. Users with this role get passwordless sudo. Users with <code class="" data-line="">Virtual Machine User Login</code> get a regular shell.</p>
<p>The mapping is enforced by <code class="" data-line="">pam_aad</code> checking the <code class="" data-line="">groups</code> claim in the token against the configured admin group. No <code class="" data-line="">/etc/sudoers.d/</code> files needed.</p>
<hr />
<h2 id="debugging-entra-id-linux-logins">Debugging Entra ID Linux Logins</h2>
<pre><code class="" data-line=""># Check aad-auth service status
systemctl status aad-auth

# View aad-auth logs
journalctl -u aad-auth -f

# Attempt a manual token validation (requires aad-auth debug mode)
aad-auth login --username vamshi@corp.com

# Check the local user cache
getent passwd vamshi_corp_com
# Returns if the user has logged in before

# Clear the local cache (forces re-authentication)
aad-auth clean-cache

# Verify Conditional Access isn&#039;t blocking (check Entra ID Sign-in logs)
# Azure Portal → Entra ID → Sign-in logs → filter by user + app &quot;Azure Linux VM Sign-In&quot;
</code></pre>
<p>The Entra ID Sign-in logs in the Azure Portal show every authentication attempt, the Conditional Access policies that evaluated, which ones passed/failed, and the exact failure reason. This is far more diagnostic than reading PAM logs.</p>
<hr />
<h2 id="entra-id-connect-bringing-on-prem-users-to-entra-id">Entra ID Connect: Bringing On-Prem Users to Entra ID</h2>
<p>For organizations with existing on-prem AD who want to enable Entra ID Linux login:</p>
<pre><code class="" data-line="">On-prem AD users → Entra ID Connect sync → Entra ID
                                                │
                                    Linux VM login (aad-auth)
</code></pre>
<p>Entra ID Connect is a Windows Server application that syncs users from on-prem AD to Entra ID every 30 minutes. Users authenticate against Entra ID (which validates against AD via Password Hash Sync, Pass-Through Authentication, or Federation). The Linux VM doesn&#8217;t know or care — it sees an Entra ID token.</p>
<p>With Password Hash Sync: password hashes (not plaintext) are synced to Entra ID — users authenticate directly in the cloud.<br />
With Pass-Through Authentication: Entra ID forwards authentication requests to an on-prem agent that validates against AD — no password hashes leave the datacenter.<br />
With Federation (AD FS / Entra ID as a relying party): Entra ID delegates authentication to AD FS — the most complex, the most on-prem control.</p>
<hr />
<h2 id="common-misconceptions"><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;" /> Common Misconceptions</h2>
<p><strong>&#8220;Entra ID = Azure Active Directory = Active Directory.&#8221;</strong> Three different things. Active Directory: on-prem, LDAP+Kerberos. Azure AD (now Entra ID): cloud, OIDC+OAuth2. Azure AD Domain Services: managed AD replica in Azure, LDAP+Kerberos, not Entra ID.</p>
<p><strong>&#8220;You need Azure AD DS to join Linux to Azure.&#8221;</strong> Azure AD DS is the managed AD service. Entra ID Linux login (via aad-auth) is entirely separate and doesn&#8217;t require AD DS. You can authenticate Linux to Entra ID directly via OIDC.</p>
<p><strong>&#8220;The Linux username matches the Entra ID username.&#8221;</strong> The UPN is sanitized (<code class="" data-line="">@</code> → <code class="" data-line="">_</code>) to produce a valid Linux username. The canonical identity is the UPN or the Entra Object ID. Don&#8217;t hardcode the sanitized username in scripts.</p>
<hr />
<h2 id="framework-alignment">Framework Alignment</h2>
<table>
<thead>
<tr>
<th>Domain</th>
<th>Relevance</th>
</tr>
</thead>
<tbody>
<tr>
<td>CISSP Domain 5: Identity and Access Management</td>
<td>Entra ID Linux login centralizes Linux VM access in the same IAM system as all other enterprise resources — one policy engine, one audit log</td>
</tr>
<tr>
<td>CISSP Domain 3: Security Architecture and Engineering</td>
<td>Eliminating per-VM local accounts removes a class of credential management risk — no SSH keys to rotate, no local accounts to audit</td>
</tr>
<tr>
<td>CISSP Domain 1: Security and Risk Management</td>
<td>Conditional Access Policies enforcing MFA on Linux logins reduce the risk of credential-based compromise of cloud VMs</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="key-takeaways">Key Takeaways</h2>
<ul>
<li>Entra ID Linux login uses OIDC device code flow — no LDAP, no Kerberos, no local Linux accounts</li>
<li><code class="" data-line="">aad-auth</code> package installs <code class="" data-line="">pam_aad.so</code> and handles the full authentication stack: token issuance, validation, user cache, UID mapping</li>
<li>VM access is controlled by Azure RBAC roles (<code class="" data-line="">Virtual Machine Administrator Login</code> / <code class="" data-line="">Virtual Machine User Login</code>) — not by sudoers files</li>
<li>Conditional Access Policies apply to Linux VM logins — MFA, device compliance, and location restrictions use the same engine as every other Entra ID app</li>
<li>Debugging starts in Entra ID Sign-in logs (Azure Portal), not in <code class="" data-line="">/var/log/auth.log</code></li>
</ul>
<hr />
<h2 id="whats-next">What&#8217;s Next</h2>
<p>EP12 showed how Entra ID enables Linux logins in the cloud. EP13 is the series closer: Zero Trust identity — what it means to verify identity continuously, how SPIFFE and SPIRE handle workload (non-human) identity, and where the stack goes from <code class="" data-line="">/etc/passwd</code> in 1970 to a Zero Trust policy engine in 2026.</p>
<p><em>Next: <a href="/zero-trust-identity-spiffe-spire/">Zero Trust Identity: SPIFFE, SPIRE, mTLS, and Continuous Verification</a></em></p>
<p>Get EP13 in your inbox when it publishes → <a href="https://linuxcent.com/subscribe">linuxcent.com/subscribe</a></p>
<p><a class="a2a_button_mastodon" href="https://www.addtoany.com/add_to/mastodon?linkurl=https%3A%2F%2Flinuxcent.com%2Fentra-id-linux-login%2F&amp;linkname=Entra%20ID%20Linux%20Login%3A%20SSH%20Authentication%20with%20Azure%20AD%20Credentials" 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%2Fentra-id-linux-login%2F&amp;linkname=Entra%20ID%20Linux%20Login%3A%20SSH%20Authentication%20with%20Azure%20AD%20Credentials" 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%2Fentra-id-linux-login%2F&amp;linkname=Entra%20ID%20Linux%20Login%3A%20SSH%20Authentication%20with%20Azure%20AD%20Credentials" 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%2Fentra-id-linux-login%2F&amp;linkname=Entra%20ID%20Linux%20Login%3A%20SSH%20Authentication%20with%20Azure%20AD%20Credentials" 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%2Fentra-id-linux-login%2F&amp;linkname=Entra%20ID%20Linux%20Login%3A%20SSH%20Authentication%20with%20Azure%20AD%20Credentials" 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%2Fentra-id-linux-login%2F&amp;linkname=Entra%20ID%20Linux%20Login%3A%20SSH%20Authentication%20with%20Azure%20AD%20Credentials" 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%2Fentra-id-linux-login%2F&amp;linkname=Entra%20ID%20Linux%20Login%3A%20SSH%20Authentication%20with%20Azure%20AD%20Credentials" 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%2Fentra-id-linux-login%2F&#038;title=Entra%20ID%20Linux%20Login%3A%20SSH%20Authentication%20with%20Azure%20AD%20Credentials" data-a2a-url="https://linuxcent.com/entra-id-linux-login/" data-a2a-title="Entra ID Linux Login: SSH Authentication with Azure AD Credentials"></a></p><p>The post <a href="https://linuxcent.com/entra-id-linux-login/">Entra ID Linux Login: SSH Authentication with Azure AD Credentials</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://linuxcent.com/entra-id-linux-login/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1805</post-id>	</item>
		<item>
		<title>How Active Directory Works: LDAP, Kerberos, and Group Policy Under the Hood</title>
		<link>https://linuxcent.com/active-directory-ldap-kerberos/</link>
					<comments>https://linuxcent.com/active-directory-ldap-kerberos/#respond</comments>
		
		<dc:creator><![CDATA[Vamshi Krishna Santhapuri]]></dc:creator>
		<pubDate>Thu, 07 May 2026 11:00:00 +0000</pubDate>
				<category><![CDATA[Identity & Authentication]]></category>
		<category><![CDATA[Active Directory]]></category>
		<category><![CDATA[Identity Management]]></category>
		<category><![CDATA[Kerberos]]></category>
		<category><![CDATA[LDAP]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Security]]></category>
		<category><![CDATA[Windows]]></category>
		<guid isPermaLink="false">https://linuxcent.com/?p=1796</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"> 6</span> <span class="rt-label rt-postfix">minutes</span></span>How Active Directory works under the hood: LDAP storage, Kerberos authentication, USN replication, KCC site topology, GPO delivery — and how Linux joins it.</p>
<p>The post <a href="https://linuxcent.com/active-directory-ldap-kerberos/">How Active Directory Works: LDAP, Kerberos, and Group Policy Under the Hood</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"> 6</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>
<p><em>The Identity Stack, Episode 9</em><br />
<a href="/freeipa-linux-identity-management/">EP08: FreeIPA</a> → <strong>EP09</strong> → <a href="/saml-vs-oidc-vs-oauth2/">EP10: SAML/OIDC</a> → &#8230;</p>
<hr />
<h2 id="tldr">TL;DR</h2>
<ul>
<li>Active Directory is not a product that happens to use LDAP — it <em>is</em> an LDAP directory with a Microsoft-extended schema, a built-in Kerberos KDC, and DNS tightly integrated</li>
<li>Replication uses USNs (Update Sequence Numbers) and GUIDs — the Knowledge Consistency Checker (KCC) automatically builds the replication topology</li>
<li>Sites and site links tell AD which DCs are physically close — AD prefers to authenticate users against a DC in the same site to minimize WAN latency</li>
<li>Group Policy Objects (GPOs) are stored as LDAP entries (in the <code class="" data-line="">CN=Policies</code> container) and Sysvol files — LDAP tells clients which GPOs apply; Sysvol delivers the policy files</li>
<li>Linux joins AD via <code class="" data-line="">realm join</code> (uses adcli + SSSD) or <code class="" data-line="">net ads join</code> (Samba + winbind) — both register a machine account in AD and get a Kerberos keytab</li>
<li>The difference between Linux in AD and Linux in FreeIPA: AD is optimized for Windows; FreeIPA is optimized for Linux — both interoperate</li>
</ul>
<hr />
<h2 id="the-big-picture-what-ad-actually-is">The Big Picture: What AD Actually Is</h2>
<pre><code class="" data-line="">Active Directory Domain: corp.com
┌────────────────────────────────────────────────────────────┐
│                                                            │
│  LDAP directory          Kerberos KDC                      │
│  ─────────────           ──────────                        │
│  Schema: 1000+ classes   Realm: CORP.COM                   │
│  Objects: users, groups, Issues TGTs + service tickets     │
│  computers, GPOs, OUs    Uses LDAP as the account DB       │
│                                                            │
│  DNS                     Sysvol (DFS share)                │
│  ────                    ────────────────                  │
│  SRV records for KDC     GPO templates                     │
│  and LDAP discovery      Login scripts                     │
│                          Replicated via DFSR               │
│                                                            │
│  Replication engine: USN + GUID + KCC                      │
└────────────────────────────────────────────────────────────┘
          │ replicates to          │ replicates to
          ▼                        ▼
   DC: dc02.corp.com        DC: dc03.corp.com
</code></pre>
<p>EP08 showed FreeIPA as the Linux-native answer to enterprise identity. AD is the Microsoft answer — and because most enterprises run Windows clients, understanding AD is unavoidable for Linux infrastructure engineers. This episode goes behind the LDAP and Kerberos protocols to explain what makes AD specifically work.</p>
<hr />
<h2 id="the-ad-schema-ldap-with-1000-object-classes">The AD Schema: LDAP With 1000+ Object Classes</h2>
<p>AD&#8217;s schema extends the base LDAP schema with Microsoft-specific classes and attributes. Every user object is a <code class="" data-line="">user</code> class (which extends <code class="" data-line="">organizationalPerson</code> which extends <code class="" data-line="">person</code> which extends <code class="" data-line="">top</code>) with additional attributes like:</p>
<pre><code class="" data-line="">sAMAccountName   ← the pre-Windows 2000 login name (vamshi)
userPrincipalName ← the modern UPN (vamshi@corp.com)
objectGUID       ← a globally unique 128-bit identifier (never changes, even if DN changes)
objectSid        ← Windows Security Identifier (used for ACL enforcement on Windows)
whenCreated      ← creation timestamp
pwdLastSet       ← password change timestamp
userAccountControl ← bitmask: disabled, locked, password never expires, etc.
memberOf         ← back-link: groups this user belongs to
</code></pre>
<p><code class="" data-line="">objectGUID</code> is the authoritative identifier in AD — not the DN. When a user is renamed or moved to a different OU, the GUID stays the same. Applications that store a user&#8217;s DN will break on rename; applications that store the GUID won&#8217;t.</p>
<p><code class="" data-line="">userAccountControl</code> is the bitmask that controls account state:</p>
<pre><code class="" data-line="">Flag          Value   Meaning
ACCOUNTDISABLE  2     Account disabled
LOCKOUT         16    Account locked out
PASSWD_NOTREQD  32    Password not required
NORMAL_ACCOUNT  512   Normal user account (set on almost all accounts)
DONT_EXPIRE_PASSWD 65536  Password never expires
</code></pre>
<pre><code class="" data-line=""># Query AD from a Linux machine
ldapsearch -x -H ldap://dc.corp.com \
  -D &quot;vamshi@corp.com&quot; -w password \
  -b &quot;dc=corp,dc=com&quot; \
  &quot;(sAMAccountName=vamshi)&quot; \
  sAMAccountName userPrincipalName objectGUID memberOf userAccountControl
</code></pre>
<hr />
<h2 id="replication-usn-guid-kcc">Replication: USN + GUID + KCC</h2>
<p>AD replication is multi-master — every DC accepts writes. The replication engine uses:</p>
<p><strong>USN (Update Sequence Number)</strong> — a per-DC counter that increments on every local write. Each attribute in the directory stores the USN at which it was last modified (<code class="" data-line="">uSNChanged</code>, <code class="" data-line="">uSNCreated</code>). When DC-A replicates to DC-B, DC-B asks: &#8220;give me everything you&#8217;ve changed since the last USN I saw from you.&#8221;</p>
<p><strong>GUID</strong> — each object has a globally unique identifier. If the same attribute is modified on two DCs before replication (a conflict), the conflict is resolved: last-writer-wins at the attribute level, based on the modification timestamp. If timestamps are equal, the attribute value from the DC with the lexicographically higher GUID wins.</p>
<p><strong>KCC (Knowledge Consistency Checker)</strong> — a component that runs on every DC and automatically constructs the replication topology. You don&#8217;t configure which DCs replicate to which — the KCC builds a minimum spanning tree that ensures every DC is connected to every other within a set number of hops. You configure Sites and site links; the KCC does the rest.</p>
<pre><code class="" data-line=""># Check replication status from a Linux machine (requires rpcclient or adcli)
# Or on the DC: repadmin /showrepl (Windows tool)

# Simulate: query the highestCommittedUSN from a DC
ldapsearch -x -H ldap://dc.corp.com \
  -D &quot;vamshi@corp.com&quot; -w password \
  -b &quot;&quot; -s base highestCommittedUSN
</code></pre>
<hr />
<h2 id="sites-and-site-links">Sites and Site Links</h2>
<p>Sites are AD&#8217;s concept of physical network topology. A site is a set of IP subnets with high-bandwidth connectivity between them. Site links represent the WAN connections between sites.</p>
<pre><code class="" data-line="">Site: Mumbai              Site: Hyderabad
┌────────────────┐        ┌────────────────┐
│ DC: dc-mum-01  │        │ DC: dc-hyd-01  │
│ DC: dc-mum-02  │        │ DC: dc-hyd-02  │
│ subnet: 10.1/16│        │ subnet: 10.2/16│
└───────┬────────┘        └────────┬───────┘
        │                          │
        └──── Site Link ───────────┘
              Cost: 100
              Replication interval: 15 min
</code></pre>
<p>When a user in Mumbai authenticates, AD&#8217;s KDC locates a DC in the same site using DNS SRV records. The SRV records include the site name in the service name: <code class="" data-line="">_ldap._tcp.Mumbai._sites.dc._msdcs.corp.com</code>. SSSD and Windows clients query site-local SRV records first.</p>
<p>If no DC is available in the local site, authentication falls back to a DC in another site across the WAN link. Configuring sites correctly prevents remote authentication failures from killing local operations.</p>
<hr />
<h2 id="group-policy-ldap-sysvol">Group Policy: LDAP + Sysvol</h2>
<p>GPOs are stored in two places:</p>
<p><strong>LDAP</strong> — the <code class="" data-line="">CN=Policies,CN=System,DC=corp,DC=com</code> container holds GPO metadata objects. Each GPO has a GUID, a display name, and version numbers. The <code class="" data-line="">gPLink</code> attribute on OUs and the domain root links GPOs to where they apply.</p>
<p><strong>Sysvol</strong> — the actual policy templates and scripts live in <code class="" data-line="">\\corp.com\SYSVOL\corp.com\Policies\{GPO-GUID}\</code>. Sysvol is a DFS-R (Distributed File System Replication) share replicated to every DC.</p>
<p>When a Windows client applies Group Policy:<br />
1. LDAP query: what GPOs are linked to my OU chain?<br />
2. Sysvol fetch: download the policy templates from the GPO&#8217;s Sysvol path<br />
3. Apply: process Registry settings, Security settings, Scripts</p>
<p>Linux clients don&#8217;t process GPOs natively. The <code class="" data-line="">adcli</code> and <code class="" data-line="">sssd</code> tools interpret a small subset of AD policy (password policy, account lockout) via LDAP. Full GPO processing on Linux requires Samba&#8217;s <code class="" data-line="">samba-gpupdate</code> or third-party tools.</p>
<hr />
<h2 id="joining-linux-to-ad">Joining Linux to AD</h2>
<h3 id="realm-join-recommended">realm join (recommended)</h3>
<pre><code class="" data-line=""># Install required packages
dnf install -y realmd sssd adcli samba-common

# Discover the domain
realm discover corp.com
# corp.com
#   type: kerberos
#   realm-name: CORP.COM
#   domain-name: corp.com
#   configured: no
#   server-software: active-directory
#   client-software: sssd

# Join
realm join corp.com -U Administrator
# Prompts for Administrator password
# Creates machine account in AD
# Configures sssd.conf, krb5.conf, nsswitch.conf, pam.d automatically

# Verify
realm list
id vamshi@corp.com
</code></pre>
<h3 id="what-the-join-does">What the join does:</h3>
<ol>
<li>Creates a machine account <code class="" data-line="">HOSTNAME$</code> in <code class="" data-line="">CN=Computers,DC=corp,DC=com</code></li>
<li>Sets a machine password (rotated automatically by SSSD)</li>
<li>Retrieves a Kerberos keytab to <code class="" data-line="">/etc/krb5.keytab</code></li>
<li>Configures SSSD with <code class="" data-line="">id_provider = ad</code>, <code class="" data-line="">auth_provider = ad</code></li>
<li>Updates <code class="" data-line="">/etc/nsswitch.conf</code> to include <code class="" data-line="">sss</code></li>
<li>Updates <code class="" data-line="">/etc/pam.d/</code> to include <code class="" data-line="">pam_sss</code></li>
</ol>
<p>After joining, SSSD uses the machine&#8217;s Kerberos keytab to authenticate to the DC and query LDAP — no hardcoded service account credentials required.</p>
<hr />
<h2 id="ldap-queries-against-ad-from-linux">LDAP Queries Against AD from Linux</h2>
<pre><code class="" data-line=""># Find a user (after kinit or with -w password)
ldapsearch -Y GSSAPI -H ldap://dc.corp.com \
  -b &quot;dc=corp,dc=com&quot; \
  &quot;(sAMAccountName=vamshi)&quot; \
  sAMAccountName mail memberOf

# Find all members of a group
ldapsearch -Y GSSAPI -H ldap://dc.corp.com \
  -b &quot;dc=corp,dc=com&quot; \
  &quot;(cn=engineers)&quot; \
  member

# Find all AD-joined Linux machines
ldapsearch -Y GSSAPI -H ldap://dc.corp.com \
  -b &quot;dc=corp,dc=com&quot; \
  &quot;(&amp;(objectClass=computer)(operatingSystem=*Linux*))&quot; \
  cn operatingSystem lastLogonTimestamp

# Find disabled accounts
ldapsearch -Y GSSAPI -H ldap://dc.corp.com \
  -b &quot;dc=corp,dc=com&quot; \
  &quot;(userAccountControl:1.2.840.113556.1.4.803:=2)&quot; \
  sAMAccountName
</code></pre>
<p>The last filter uses an LDAP extensible match (<code class="" data-line="">1.2.840.113556.1.4.803</code> is the OID for bitwise AND). <code class="" data-line="">userAccountControl:1.2.840.113556.1.4.803:=2</code> means &#8220;entries where userAccountControl AND 2 equals 2&#8221; — i.e., the ACCOUNTDISABLE bit is set. This is a Microsoft AD extension not in standard LDAP.</p>
<hr />
<h2 id="common-misconceptions"><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;" /> Common Misconceptions</h2>
<p><strong>&#8220;AD is just Microsoft&#8217;s LDAP.&#8221;</strong> AD is LDAP + Kerberos + DNS + DFS-R + GPO, all tightly integrated and with a schema that the Microsoft ecosystem depends on. You can query AD with standard <code class="" data-line="">ldapsearch</code>. You cannot replace it with OpenLDAP without breaking every Windows client.</p>
<p><strong>&#8220;Linux machines in AD get GPO.&#8221;</strong> Linux machines appear in AD and can be organized into OUs. Standard GPOs don&#8217;t apply to them. Samba&#8217;s <code class="" data-line="">samba-gpupdate</code> can process a subset of AD policy for Linux — mostly Registry and Security settings mapped to Linux equivalents.</p>
<p><strong>&#8220;realm leave removes the machine cleanly.&#8221;</strong> <code class="" data-line="">realm leave</code> removes local configuration but does not delete the machine account from AD. The stale computer object stays in <code class="" data-line="">CN=Computers</code> until an AD admin deletes it. Always run <code class="" data-line="">realm leave &amp;&amp; adcli delete-computer -U Administrator</code> for a clean removal.</p>
<hr />
<h2 id="framework-alignment">Framework Alignment</h2>
<table>
<thead>
<tr>
<th>Domain</th>
<th>Relevance</th>
</tr>
</thead>
<tbody>
<tr>
<td>CISSP Domain 5: Identity and Access Management</td>
<td>AD is the dominant enterprise identity store — understanding its LDAP structure, Kerberos realm, and GPO model is essential for IAM in mixed environments</td>
</tr>
<tr>
<td>CISSP Domain 4: Communications and Network Security</td>
<td>AD replication traffic (RPC, LDAP, Kerberos) is a significant portion of enterprise WAN traffic — Sites and site links are a network security and performance design decision</td>
</tr>
<tr>
<td>CISSP Domain 3: Security Architecture and Engineering</td>
<td>AD forest/domain/OU hierarchy is an architectural decision with long-term security consequences — getting OU structure wrong constrains GPO delegation for years</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="key-takeaways">Key Takeaways</h2>
<ul>
<li>AD is LDAP + Kerberos + DNS + GPO + DFS-R — not a product that &#8220;uses&#8221; these; they&#8217;re the implementation</li>
<li>Replication is multi-master via USN + GUID; the KCC builds the topology automatically from Sites configuration</li>
<li><code class="" data-line="">objectGUID</code> is the stable identifier — not the DN, which changes on rename/move</li>
<li><code class="" data-line="">realm join</code> is the correct way to join Linux to AD — it configures SSSD, Kerberos, PAM, and NSS correctly in one command</li>
<li><code class="" data-line="">userAccountControl</code> is the bitmask that controls account state — <code class="" data-line="">(userAccountControl:1.2.840.113556.1.4.803:=2)</code> finds disabled accounts</li>
</ul>
<hr />
<h2 id="whats-next">What&#8217;s Next</h2>
<p>EP09 covered AD — LDAP and Kerberos inside the corporate network. EP10 covers what happens when identity needs to work across the internet, where Kerberos doesn&#8217;t reach: SAML, OAuth2, and OIDC — the protocols that let identity leave the building.</p>
<p><em>Next: <a href="/saml-vs-oidc-vs-oauth2/">SAML vs OIDC vs OAuth2: Which Protocol Handles Which Identity Problem</a></em></p>
<p>Get EP10 in your inbox when it publishes → <a href="https://linuxcent.com/subscribe">linuxcent.com/subscribe</a></p>
<p><a class="a2a_button_mastodon" href="https://www.addtoany.com/add_to/mastodon?linkurl=https%3A%2F%2Flinuxcent.com%2Factive-directory-ldap-kerberos%2F&amp;linkname=How%20Active%20Directory%20Works%3A%20LDAP%2C%20Kerberos%2C%20and%20Group%20Policy%20Under%20the%20Hood" 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%2Factive-directory-ldap-kerberos%2F&amp;linkname=How%20Active%20Directory%20Works%3A%20LDAP%2C%20Kerberos%2C%20and%20Group%20Policy%20Under%20the%20Hood" 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%2Factive-directory-ldap-kerberos%2F&amp;linkname=How%20Active%20Directory%20Works%3A%20LDAP%2C%20Kerberos%2C%20and%20Group%20Policy%20Under%20the%20Hood" 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%2Factive-directory-ldap-kerberos%2F&amp;linkname=How%20Active%20Directory%20Works%3A%20LDAP%2C%20Kerberos%2C%20and%20Group%20Policy%20Under%20the%20Hood" 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%2Factive-directory-ldap-kerberos%2F&amp;linkname=How%20Active%20Directory%20Works%3A%20LDAP%2C%20Kerberos%2C%20and%20Group%20Policy%20Under%20the%20Hood" 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%2Factive-directory-ldap-kerberos%2F&amp;linkname=How%20Active%20Directory%20Works%3A%20LDAP%2C%20Kerberos%2C%20and%20Group%20Policy%20Under%20the%20Hood" 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%2Factive-directory-ldap-kerberos%2F&amp;linkname=How%20Active%20Directory%20Works%3A%20LDAP%2C%20Kerberos%2C%20and%20Group%20Policy%20Under%20the%20Hood" 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%2Factive-directory-ldap-kerberos%2F&#038;title=How%20Active%20Directory%20Works%3A%20LDAP%2C%20Kerberos%2C%20and%20Group%20Policy%20Under%20the%20Hood" data-a2a-url="https://linuxcent.com/active-directory-ldap-kerberos/" data-a2a-title="How Active Directory Works: LDAP, Kerberos, and Group Policy Under the Hood"></a></p><p>The post <a href="https://linuxcent.com/active-directory-ldap-kerberos/">How Active Directory Works: LDAP, Kerberos, and Group Policy Under the Hood</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://linuxcent.com/active-directory-ldap-kerberos/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1796</post-id>	</item>
		<item>
		<title>FreeIPA: LDAP + Kerberos + PKI in a Single Linux Identity Stack</title>
		<link>https://linuxcent.com/freeipa-linux-identity-management/</link>
					<comments>https://linuxcent.com/freeipa-linux-identity-management/#respond</comments>
		
		<dc:creator><![CDATA[Vamshi Krishna Santhapuri]]></dc:creator>
		<pubDate>Thu, 07 May 2026 05:00:00 +0000</pubDate>
				<category><![CDATA[Identity & Authentication]]></category>
		<category><![CDATA[FreeIPA]]></category>
		<category><![CDATA[HBAC]]></category>
		<category><![CDATA[Identity Management]]></category>
		<category><![CDATA[Kerberos]]></category>
		<category><![CDATA[LDAP]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[linux-security]]></category>
		<guid isPermaLink="false">https://linuxcent.com/?p=1793</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"> 5</span> <span class="rt-label rt-postfix">minutes</span></span>FreeIPA combines 389-DS, MIT Kerberos, Dogtag PKI, and Bind DNS into one Linux identity stack. Set up HBAC rules, centralized sudo, and AD trust.</p>
<p>The post <a href="https://linuxcent.com/freeipa-linux-identity-management/">FreeIPA: LDAP + Kerberos + PKI in a Single Linux Identity Stack</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"> 5</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>
<p><em>The Identity Stack, Episode 8</em><br />
<a href="/ldap-high-availability/">EP07: LDAP HA</a> → <strong>EP08</strong> → <a href="/active-directory-ldap-kerberos/">EP09: Active Directory</a> → &#8230;</p>
<hr />
<h2 id="tldr">TL;DR</h2>
<ul>
<li>FreeIPA is 389-DS (LDAP) + MIT Kerberos + Dogtag PKI + Bind DNS + SSSD — one <code class="" data-line="">ipa-server-install</code> command gets you an enterprise identity platform</li>
<li>Host-Based Access Control (HBAC) lets you define centrally: which users can SSH to which hosts — no more managing <code class="" data-line="">/etc/security/access.conf</code> per machine</li>
<li>Sudo rules from the directory: define <code class="" data-line="">sudo</code> policy centrally, have every machine pull it — no <code class="" data-line="">/etc/sudoers.d/</code> files scattered across the fleet</li>
<li><code class="" data-line="">ipa</code> CLI is the management interface — <code class="" data-line="">ipa user-add</code>, <code class="" data-line="">ipa group-add</code>, <code class="" data-line="">ipa hbacrule-add</code> — everything that took five LDAP commands takes one <code class="" data-line="">ipa</code> command</li>
<li>FreeIPA trusts with Active Directory let Linux machines authenticate AD users without joining the AD domain</li>
<li>The right choice for Linux-centric environments; AD is the right choice when Windows clients dominate</li>
</ul>
<hr />
<h2 id="the-big-picture-what-freeipa-integrates">The Big Picture: What FreeIPA Integrates</h2>
<pre><code class="" data-line="">┌─────────────────────────────────────────────────────────┐
│                    FreeIPA Server                        │
│                                                         │
│  389-DS (LDAP)    MIT Kerberos    Dogtag PKI            │
│  ─────────────    ───────────     ─────────             │
│  User/group       TGT + service   Machine certs         │
│  storage          ticket issuing  User certs             │
│                                   OCSP / CRL            │
│  Bind DNS         SSSD (client)   Apache (WebUI)        │
│  ──────────       ────────────    ──────────────        │
│  SRV records      Enrollment      Management UI         │
│  for KDC/LDAP     automation      REST API              │
└─────────────────────────────────────────────────────────┘
              ▲                  ▲
              │ enrollment       │ SSH + sudo rules
   ┌──────────┴──────────┐  ┌───┴──────────────────┐
   │  Linux client        │  │  Linux client         │
   │  (ipa-client-install)│  │  (ipa-client-install) │
   └─────────────────────┘  └──────────────────────┘
</code></pre>
<p>EP06 and EP07 built OpenLDAP from components. FreeIPA gives you all of that plus Kerberos, PKI, DNS, and HBAC — opinionated, integrated, and managed through a single CLI and WebUI. This episode shows what you actually get from it.</p>
<hr />
<h2 id="why-freeipa-instead-of-bare-openldap">Why FreeIPA Instead of Bare OpenLDAP</h2>
<p>Running bare OpenLDAP requires you to:<br />
&#8211; Configure schema for POSIX accounts, SSH keys, sudo rules, HBAC manually<br />
&#8211; Set up MIT Kerberos separately and integrate it with LDAP<br />
&#8211; Build your own PKI for machine certificates<br />
&#8211; Maintain DNS SRV records for Kerberos discovery<br />
&#8211; Write client enrollment scripts<br />
&#8211; Build a management interface (or live in LDIF)</p>
<p>FreeIPA does all of this in one installer, with a consistent data model across all components. The trade-off is opacity — FreeIPA makes decisions for you (schema, replication topology, Kerberos realm name) that bare OpenLDAP leaves to you.</p>
<hr />
<h2 id="installing-freeipa-server">Installing FreeIPA Server</h2>
<pre><code class="" data-line=""># RHEL / Rocky / AlmaLinux
dnf install -y freeipa-server freeipa-server-dns

# Run the installer (interactive)
ipa-server-install

# Or non-interactive:
ipa-server-install \
  --realm=CORP.COM \
  --domain=corp.com \
  --ds-password=DM_password \
  --admin-password=Admin_password \
  --setup-dns \
  --forwarder=8.8.8.8 \
  --unattended

# After install: get an admin Kerberos ticket
kinit admin
</code></pre>
<p>The installer creates:<br />
&#8211; 389-DS instance with the FreeIPA schema<br />
&#8211; MIT KDC with realm <code class="" data-line="">CORP.COM</code><br />
&#8211; Dogtag CA and all certificate infrastructure<br />
&#8211; Bind DNS with SRV records for the KDC and LDAP server<br />
&#8211; Apache WebUI at <code class="" data-line="">https://ipa.corp.com/ipa/ui/</code><br />
&#8211; SSSD configured on the server itself</p>
<p>Time: 5–10 minutes. What used to take a week of manual configuration.</p>
<hr />
<h2 id="the-ipa-cli">The ipa CLI</h2>
<p>Every management action goes through <code class="" data-line="">ipa</code>. It talks to the IPA server&#8217;s REST API and handles Kerberos authentication transparently (it uses your <code class="" data-line="">kinit</code> session).</p>
<pre><code class="" data-line=""># Users
ipa user-add vamshi \
  --first=Vamshi --last=Krishna \
  --email=vamshi@corp.com \
  --password

ipa user-show vamshi
ipa user-find --all              # search all users
ipa user-disable vamshi          # lock account without deleting
ipa user-mod vamshi --shell=/bin/zsh

# Groups
ipa group-add engineers --desc &quot;Engineering team&quot;
ipa group-add-member engineers --users=vamshi,alice

# Password policy
ipa pwpolicy-mod --minlength=12 --maxlife=90 --history=10

# SSH public keys — stored centrally, pushed to every host
ipa user-mod vamshi --sshpubkey=&quot;ssh-ed25519 AAAA...&quot;
# SSSD on enrolled hosts will use this key for SSH login — no authorized_keys file needed
</code></pre>
<hr />
<h2 id="host-based-access-control-hbac">Host-Based Access Control (HBAC)</h2>
<p>HBAC is the feature that justifies FreeIPA for most Linux shops. It lets you define centrally: which users (or groups) can log in to which hosts (or host groups), using which services (SSH, sudo, FTP).</p>
<p>Without HBAC, access control is per-machine: <code class="" data-line="">/etc/security/access.conf</code> or PAM <code class="" data-line="">pam_access</code> rules, replicated across every server, managed inconsistently.</p>
<p>With HBAC: one rule, enforced everywhere.</p>
<pre><code class="" data-line=""># Create host groups
ipa hostgroup-add production-servers --desc &quot;Production Linux hosts&quot;
ipa hostgroup-add-member production-servers --hosts=web01.corp.com,db01.corp.com

# Create user groups
ipa group-add sre-team
ipa group-add-member sre-team --users=vamshi,alice

# Create an HBAC rule
ipa hbacrule-add allow-sre-to-prod \
  --desc &quot;SRE team can SSH to production&quot;
ipa hbacrule-add-user allow-sre-to-prod --groups=sre-team
ipa hbacrule-add-host allow-sre-to-prod --hostgroups=production-servers
ipa hbacrule-add-service allow-sre-to-prod --hbacsvcs=sshd

# Test the rule before applying
ipa hbactest \
  --user=vamshi \
  --host=web01.corp.com \
  --service=sshd
# Access granted: True
# Matched rules: allow-sre-to-prod
</code></pre>
<p>SSSD on each enrolled host enforces the HBAC rules at login time by querying the IPA server. No per-machine configuration. Add a new server to the <code class="" data-line="">production-servers</code> host group and the HBAC rules apply immediately.</p>
<hr />
<h2 id="sudo-rules-from-the-directory">Sudo Rules from the Directory</h2>
<pre><code class="" data-line=""># Create a sudo rule
ipa sudorule-add allow-sre-sudo \
  --cmdcat=all \
  --desc &quot;SRE team gets full sudo on production&quot;
ipa sudorule-add-user allow-sre-sudo --groups=sre-team
ipa sudorule-add-host allow-sre-sudo --hostgroups=production-servers

# Or a scoped rule — only specific commands
ipa sudorule-add allow-service-restart
ipa sudocmdgroup-add service-commands
ipa sudocmd-add /usr/bin/systemctl
ipa sudocmdgroup-add-member service-commands --sudocmds=&quot;/usr/bin/systemctl&quot;
ipa sudorule-add-allow-command allow-service-restart --sudocmdgroups=service-commands
</code></pre>
<p>On enrolled hosts, SSSD&#8217;s <code class="" data-line="">sssd_sudo</code> responder pulls these rules and the <code class="" data-line="">sudo</code> command evaluates them locally. No <code class="" data-line="">/etc/sudoers.d/</code> files. Central policy, local enforcement.</p>
<hr />
<h2 id="enrolling-a-client">Enrolling a Client</h2>
<pre><code class="" data-line=""># On the client machine
dnf install -y freeipa-client

ipa-client-install \
  --domain=corp.com \
  --server=ipa.corp.com \
  --realm=CORP.COM \
  --principal=admin \
  --password=Admin_password \
  --unattended

# What this does:
# 1. Registers the host in IPA as a machine principal
# 2. Retrieves a host Kerberos keytab (/etc/krb5.keytab)
# 3. Configures SSSD (sssd.conf, nsswitch.conf, pam.d)
# 4. Configures Kerberos (/etc/krb5.conf)
# 5. Optionally configures NTP and DNS
</code></pre>
<p>After enrollment: <code class="" data-line="">getent passwd vamshi</code> returns the IPA user. SSH with an IPA password works. HBAC rules are enforced. Sudo rules from the directory apply. SSH public keys from the user&#8217;s IPA profile work without <code class="" data-line="">authorized_keys</code> files.</p>
<hr />
<h2 id="freeipa-trust-with-active-directory">FreeIPA Trust with Active Directory</h2>
<p>In mixed environments (Linux servers + Windows clients), you can establish a trust between FreeIPA and AD without joining the Linux servers to the AD domain directly.</p>
<pre><code class="" data-line=""># On the IPA server (after installing ipa-server-trust-ad)
ipa-adtrust-install --netbios-name=CORP

# Establish the trust
ipa trust-add ad.corp.com \
  --admin=Administrator \
  --password \
  --type=ad

# AD users can now log in to IPA-enrolled Linux hosts
# They appear as: CORP.COM\username or username@corp.com
</code></pre>
<p>Under the hood: FreeIPA acts as an SSSD-enabled Samba DC for the trust relationship. AD users&#8217; Kerberos tickets from the AD KDC are accepted by the FreeIPA KDC, which maps them to POSIX attributes stored in IPA (or automatically generated via ID mapping).</p>
<hr />
<h2 id="common-misconceptions"><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;" /> Common Misconceptions</h2>
<p><strong>&#8220;FreeIPA is just OpenLDAP with a UI.&#8221;</strong> FreeIPA uses 389-DS (not OpenLDAP), adds a full Kerberos KDC, a certificate authority, DNS, HBAC enforcement, and sudo management — all with a consistent schema designed for these use cases. It&#8217;s an integrated identity platform, not a wrapper.</p>
<p><strong>&#8220;HBAC rules replace firewall rules.&#8221;</strong> HBAC controls who can log in to a host at the authentication layer — not network access. A blocked HBAC rule means the SSH session is rejected after TCP connection. You still need firewall rules to block TCP access.</p>
<p><strong>&#8220;FreeIPA replicas are identical.&#8221;</strong> FreeIPA uses 389-DS Multi-Supplier replication. All replicas accept reads and writes. But the CA is separate — only the initial server (and explicitly designated CA replicas) run the CA. If the CA goes down, certificate operations stop; authentication does not.</p>
<hr />
<h2 id="framework-alignment">Framework Alignment</h2>
<table>
<thead>
<tr>
<th>Domain</th>
<th>Relevance</th>
</tr>
</thead>
<tbody>
<tr>
<td>CISSP Domain 5: Identity and Access Management</td>
<td>FreeIPA is an enterprise IAM platform — HBAC, sudo policy, SSH key management, and certificate-based authentication are all IAM controls</td>
</tr>
<tr>
<td>CISSP Domain 3: Security Architecture and Engineering</td>
<td>FreeIPA&#8217;s integrated CA enables certificate-based authentication for machines and users — a stronger authentication factor than passwords</td>
</tr>
<tr>
<td>CISSP Domain 1: Security and Risk Management</td>
<td>Centralized HBAC and sudo policy reduces the attack surface of privilege escalation — no more inconsistent sudoers files that drift across the fleet</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="key-takeaways">Key Takeaways</h2>
<ul>
<li>FreeIPA = 389-DS + MIT Kerberos + Dogtag PKI + Bind DNS — one installer, one management interface</li>
<li>HBAC rules define centrally who can SSH to which host groups — enforced by SSSD on every enrolled client, no per-machine config</li>
<li>Sudo rules from the directory replace scattered <code class="" data-line="">/etc/sudoers.d/</code> files — central policy, SSSD-enforced locally</li>
<li><code class="" data-line="">ipa hbactest</code> lets you verify access rules before a user hits a blocked login — use it before every policy change</li>
<li>For Linux-centric environments: FreeIPA. For Windows-dominant environments: AD. For mixed: FreeIPA trust with AD.</li>
</ul>
<hr />
<h2 id="whats-next">What&#8217;s Next</h2>
<p>FreeIPA is the Linux answer to enterprise identity. EP09 covers the Microsoft answer — Active Directory — which extended LDAP and Kerberos into a complete enterprise platform with Group Policy, Sites, and a replication model built for global scale.</p>
<p><em>Next: <a href="/active-directory-ldap-kerberos/">How Active Directory Works: LDAP, Kerberos, and Group Policy Under the Hood</a></em></p>
<p>Get EP09 in your inbox when it publishes → <a href="https://linuxcent.com/subscribe">linuxcent.com/subscribe</a></p>
<p><a class="a2a_button_mastodon" href="https://www.addtoany.com/add_to/mastodon?linkurl=https%3A%2F%2Flinuxcent.com%2Ffreeipa-linux-identity-management%2F&amp;linkname=FreeIPA%3A%20LDAP%20%2B%20Kerberos%20%2B%20PKI%20in%20a%20Single%20Linux%20Identity%20Stack" 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%2Ffreeipa-linux-identity-management%2F&amp;linkname=FreeIPA%3A%20LDAP%20%2B%20Kerberos%20%2B%20PKI%20in%20a%20Single%20Linux%20Identity%20Stack" 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%2Ffreeipa-linux-identity-management%2F&amp;linkname=FreeIPA%3A%20LDAP%20%2B%20Kerberos%20%2B%20PKI%20in%20a%20Single%20Linux%20Identity%20Stack" 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%2Ffreeipa-linux-identity-management%2F&amp;linkname=FreeIPA%3A%20LDAP%20%2B%20Kerberos%20%2B%20PKI%20in%20a%20Single%20Linux%20Identity%20Stack" 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%2Ffreeipa-linux-identity-management%2F&amp;linkname=FreeIPA%3A%20LDAP%20%2B%20Kerberos%20%2B%20PKI%20in%20a%20Single%20Linux%20Identity%20Stack" 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%2Ffreeipa-linux-identity-management%2F&amp;linkname=FreeIPA%3A%20LDAP%20%2B%20Kerberos%20%2B%20PKI%20in%20a%20Single%20Linux%20Identity%20Stack" 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%2Ffreeipa-linux-identity-management%2F&amp;linkname=FreeIPA%3A%20LDAP%20%2B%20Kerberos%20%2B%20PKI%20in%20a%20Single%20Linux%20Identity%20Stack" 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%2Ffreeipa-linux-identity-management%2F&#038;title=FreeIPA%3A%20LDAP%20%2B%20Kerberos%20%2B%20PKI%20in%20a%20Single%20Linux%20Identity%20Stack" data-a2a-url="https://linuxcent.com/freeipa-linux-identity-management/" data-a2a-title="FreeIPA: LDAP + Kerberos + PKI in a Single Linux Identity Stack"></a></p><p>The post <a href="https://linuxcent.com/freeipa-linux-identity-management/">FreeIPA: LDAP + Kerberos + PKI in a Single Linux Identity Stack</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://linuxcent.com/freeipa-linux-identity-management/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1793</post-id>	</item>
		<item>
		<title>How Kerberos Works: Tickets, KDC, and Why Enterprises Use It With LDAP</title>
		<link>https://linuxcent.com/how-kerberos-works/</link>
					<comments>https://linuxcent.com/how-kerberos-works/#respond</comments>
		
		<dc:creator><![CDATA[Vamshi Krishna Santhapuri]]></dc:creator>
		<pubDate>Mon, 04 May 2026 05:00:00 +0000</pubDate>
				<category><![CDATA[Identity & Authentication]]></category>
		<category><![CDATA[Active Directory]]></category>
		<category><![CDATA[Authentication]]></category>
		<category><![CDATA[Identity Management]]></category>
		<category><![CDATA[Kerberos]]></category>
		<category><![CDATA[LDAP]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Security]]></category>
		<guid isPermaLink="false">https://linuxcent.com/?p=1784</guid>

					<description><![CDATA[<p><span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 7</span> <span class="rt-label rt-postfix">minutes</span></span>How Kerberos works: the KDC, TGT flow, ticket-granting service, and why enterprises pair it with LDAP — explained with kinit, klist, and wire-level detail.</p>
<p>The post <a href="https://linuxcent.com/how-kerberos-works/">How Kerberos Works: Tickets, KDC, and Why Enterprises Use It With LDAP</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></description>
										<content:encoded><![CDATA[<span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 7</span> <span class="rt-label rt-postfix">minutes</span></span><style>
pre{position:relative;background:#1e1e1e;color:#d4d4d4;
    padding:16px 16px 16px 20px;border-radius:6px;overflow-x:auto;
    font-family:'JetBrains Mono','Fira Code','Cascadia Code',Consolas,'Courier New',monospace;
    font-size:.88em;line-height:1.6;border-left:4px solid #555}
code{background:#f4f4f4;padding:2px 5px;border-radius:3px;font-size:.9em}
pre code{background:transparent;padding:0;color:inherit}
pre[data-lang="bash"],pre[data-lang="sh"],
pre[data-lang="shell"],pre[data-lang="zsh"]{border-left-color:#4ec9b0}
pre[data-lang="yaml"],pre[data-lang="json"],
pre[data-lang="toml"],pre[data-lang="xml"]{border-left-color:#569cd6}
pre[data-lang="python"],pre[data-lang="go"],pre[data-lang="rust"],
pre[data-lang="java"],pre[data-lang="c"],pre[data-lang="cpp"]{border-left-color:#c586c0}
pre[data-lang="text"],pre[data-lang="output"],
pre[data-lang="console"]{border-left-color:#888}
.lc-copy-btn{position:absolute;top:8px;right:8px;background:#2d2d2d;color:#ccc;
    border:1px solid #444;border-radius:4px;padding:3px 9px;font-size:.75em;
    font-family:system-ui,sans-serif;cursor:pointer;opacity:0;
    transition:opacity .15s,background .15s;line-height:1.6}
pre:hover .lc-copy-btn{opacity:1}
.lc-copy-btn:hover{background:#3a3a3a;color:#fff}
.lc-copy-btn.copied{color:#4ec9b0;border-color:#4ec9b0}
.lc-lang-badge{position:absolute;top:8px;left:20px;font-family:system-ui,sans-serif;
    font-size:.7em;color:#666;text-transform:uppercase;letter-spacing:.04em;
    line-height:1;pointer-events:none;opacity:0;transition:opacity .15s}
pre:hover .lc-lang-badge{opacity:1}
table{border-collapse:collapse;width:100%;margin:16px 0}
th,td{border:1px solid #ddd;padding:10px 14px;text-align:left}
th{background:#f0f0f0;font-weight:600}
tr:nth-child(even){background:#fafafa}
</style>
<p><script>
(function(){
  if(window.__lcCodeEnhanced)return;
  window.__lcCodeEnhanced=true;
  function enhance(){
    document.querySelectorAll('pre').forEach(function(pre){
      var code=pre.querySelector('code');
      var lang='';
      if(code){var m=(code.className||'').match(/language-(\S+)/);if(m)lang=m[1].toLowerCase();}
      if(lang)pre.setAttribute('data-lang',lang);
      if(lang){var badge=document.createElement('span');badge.className='lc-lang-badge';badge.textContent=lang;pre.insertBefore(badge,pre.firstChild);}
      var btn=document.createElement('button');
      btn.className='lc-copy-btn';btn.textContent='Copy';btn.setAttribute('aria-label','Copy code to clipboard');
      pre.appendChild(btn);
      btn.addEventListener('click',function(){
        var text=code?code.innerText:pre.innerText;
        if(navigator.clipboard&&window.isSecureContext){
          navigator.clipboard.writeText(text).then(function(){ok(btn);}).catch(function(){fb(text,btn);});
        }else{fb(text,btn);}
      });
    });
  }
  function ok(btn){btn.textContent='Copied!';btn.classList.add('copied');setTimeout(function(){btn.textContent='Copy';btn.classList.remove('copied');},2000);}
  function fb(text,btn){
    try{var ta=document.createElement('textarea');ta.value=text;ta.style.cssText='position:fixed;left:-9999px;top:-9999px;opacity:0';document.body.appendChild(ta);ta.select();document.execCommand('copy');document.body.removeChild(ta);ok(btn);}
    catch(e){btn.textContent='✗ Failed';setTimeout(function(){btn.textContent='Copy';},2000);}
  }
  if(document.readyState==='loading'){document.addEventListener('DOMContentLoaded',enhance);}else{enhance();}
})();
</script></p>
<p><em>The Identity Stack, Episode 5</em><br />
<a href="/what-is-ldap/">EP01</a> → <a href="/ldap-internals-directory-structure/">EP02</a> → <a href="/ldap-authentication-linux-pam-nss/">EP03</a> → <a href="/sssd-linux-authentication/">EP04: SSSD</a> → <strong>EP05</strong> → <a href="/openldap-setup-replication/">EP06: OpenLDAP</a> → &#8230;</p>
<hr />
<h2 id="tldr">TL;DR</h2>
<ul>
<li>Kerberos is a network authentication protocol — it proves identity without sending passwords over the network, using time-limited cryptographic tickets</li>
<li>Three actors: the client, the KDC (Key Distribution Center), and the service — the KDC issues tickets; clients use tickets to authenticate to services</li>
<li>The ticket flow: AS-REQ (get a TGT) → TGS-REQ (exchange TGT for a service ticket) → AP-REQ (present service ticket to the target service)</li>
<li>A TGT (Ticket-Granting Ticket) is a session credential — it lets you request service tickets without re-entering your password for the lifetime of the ticket (default 10 hours)</li>
<li>LDAP + Kerberos together: LDAP stores identity (who you are), Kerberos authenticates it (proves you are who you say you are) — Active Directory is exactly this combination</li>
<li><code class="" data-line="">kinit</code>, <code class="" data-line="">klist</code>, <code class="" data-line="">kdestroy</code> are the hands-on tools — run them and read the ticket output</li>
</ul>
<hr />
<h2 id="the-big-picture-three-actors-three-steps">The Big Picture: Three Actors, Three Steps</h2>
<pre><code class="" data-line="">         1. AS-REQ / AS-REP
Client ◄────────────────────► AS (Authentication Server)
  │                                     │
  │    (part of KDC)                    │
  │                                     ▼
  │         2. TGS-REQ / TGS-REP   TGS (Ticket-Granting Server)
  ├───────────────────────────────────►│
  │         (part of KDC)              │
  │                                    │
  │    3. AP-REQ / AP-REP              │
  └─────────────────────────────► Service (SSH, LDAP, NFS, HTTP...)

KDC = AS + TGS (usually the same process, same machine)
</code></pre>
<p>EP04 mentioned Kerberos tickets and clock skew requirements without explaining the protocol. This episode explains why Kerberos was invented, what a ticket actually is, and how the three-step flow works — so that when SSSD says &#8220;KDC unreachable&#8221; or <code class="" data-line="">kinit</code> fails with &#8220;pre-authentication required,&#8221; you know exactly what&#8217;s happening.</p>
<hr />
<h2 id="the-problem-kerberos-was-built-to-solve">The Problem Kerberos Was Built to Solve</h2>
<p>MIT&#8217;s Project Athena started in 1983 — a campus-wide computing initiative giving students access to thousands of workstations. The problem: how do you authenticate a student at workstation 847 to a file server across campus without sending their password over the network?</p>
<p>In 1988, Steve Miller and Clifford Neuman published Kerberos version 4. The core insight: a trusted third party (the KDC) can issue cryptographic proof that a user has authenticated, and that proof can be presented to any service on the network without the service ever seeing the user&#8217;s password.</p>
<p>The password never leaves the client machine after the initial authentication. Every subsequent authentication — to a different service, to the same service again — uses a ticket. The KDC knows both the client and the service. The client and service only need to trust the KDC.</p>
<hr />
<h2 id="keys-tickets-and-sessions">Keys, Tickets, and Sessions</h2>
<p>Before the protocol, the primitives:</p>
<p><strong>Long-term keys</strong> — derived from passwords. When you set a password in Kerberos, it&#8217;s hashed into a key stored in the KDC database (in the <code class="" data-line="">krbtgt</code> account on AD, in <code class="" data-line="">/var/lib/krb5kdc/principal</code> on MIT Kerberos). The client also derives this key from the password at authentication time. Neither ever sends the raw password.</p>
<p><strong>Session keys</strong> — temporary symmetric keys created by the KDC for a specific session. They&#8217;re valid for the ticket&#8217;s lifetime. After the ticket expires, the session key is useless.</p>
<p><strong>Tickets</strong> — encrypted blobs issued by the KDC. A ticket contains the session key, the client identity, the expiry time, and optional flags. It&#8217;s encrypted with the target service&#8217;s long-term key — only the service can decrypt it. The client carries the ticket but can&#8217;t read the contents.</p>
<hr />
<h2 id="the-three-step-flow">The Three-Step Flow</h2>
<h3 id="step-1-as-req-as-rep-getting-a-tgt">Step 1: AS-REQ / AS-REP — Getting a TGT</h3>
<pre><code class="" data-line="">Client                        KDC (AS component)
  │                                │
  │── AS-REQ ──────────────────────►
  │   {username, timestamp}         │
  │   (timestamp encrypted with     │
  │    client&#039;s long-term key)       │
  │                                 │
  │   KDC verifies: decrypts        │
  │   timestamp with stored key.    │
  │   If valid → issues TGT         │
  │                                 │
  ◄── AS-REP ──────────────────────│
      {session_key_enc_with_client, │
       TGT_enc_with_krbtgt_key}     │
</code></pre>
<p>The client decrypts the session key using its long-term key (derived from the password). The TGT is encrypted with the KDC&#8217;s own key (<code class="" data-line="">krbtgt</code>) — the client can&#8217;t read it, but carries it.</p>
<p>This is the step that requires the password. After this, the TGT is what the client uses for everything else.</p>
<h3 id="step-2-tgs-req-tgs-rep-getting-a-service-ticket">Step 2: TGS-REQ / TGS-REP — Getting a Service Ticket</h3>
<pre><code class="" data-line="">Client                        KDC (TGS component)
  │                                │
  │── TGS-REQ ─────────────────────►
  │   {TGT, authenticator,         │
  │    target_service_name}        │
  │   (authenticator encrypted      │
  │    with TGT session key)        │
  │                                 │
  │   KDC: decrypts TGT,           │
  │   verifies authenticator,       │
  │   issues service ticket         │
  │                                 │
  ◄── TGS-REP ────────────────────│
      {service_session_key_enc,    │
       service_ticket_enc_with_    │
       service_long_term_key}      │
</code></pre>
<p>No password involved. The client proves its identity by presenting the TGT (which only the KDC can issue) and an authenticator (a timestamp encrypted with the TGT&#8217;s session key, proving the client holds the session key without revealing it).</p>
<h3 id="step-3-ap-req-ap-rep-authenticating-to-the-service">Step 3: AP-REQ / AP-REP — Authenticating to the Service</h3>
<pre><code class="" data-line="">Client                        Service (sshd, LDAP, NFS...)
  │                                │
  │── AP-REQ ──────────────────────►
  │   {service_ticket,             │
  │    authenticator_enc_with_      │
  │    service_session_key}        │
  │                                 │
  │   Service: decrypts ticket      │
  │   with its long-term key,       │
  │   verifies authenticator        │
  │                                 │
  ◄── AP-REP (optional) ───────────│
      {mutual authentication}       │
</code></pre>
<p>The service decrypts the ticket using its own key. It extracts the client identity and session key. It verifies the authenticator. No communication with the KDC required — the service trusts what the KDC signed.</p>
<hr />
<h2 id="why-clock-skew-matters">Why Clock Skew Matters</h2>
<p>Every Kerberos authenticator contains a timestamp. The service rejects authenticators older than 5 minutes (by default) — this prevents replay attacks where an attacker captures an authenticator and replays it later.</p>
<p>This is why clock skew over 5 minutes breaks Kerberos authentication entirely. If your machine&#8217;s clock drifts 6 minutes from the KDC, every authenticator you generate is rejected as too old or too far in the future. No tickets. No AD logins. No SSSD authentication.</p>
<pre><code class="" data-line=""># Check time sync status
timedatectl status
chronyc tracking        # if using chrony
ntpq -p                 # if using ntpd

# If clock is off: force a sync
chronyc makestep        # immediate step correction (chrony)
</code></pre>
<hr />
<h2 id="hands-on-kinit-klist-kdestroy">Hands-On: kinit, klist, kdestroy</h2>
<pre><code class="" data-line=""># Get a TGT (will prompt for password)
kinit vamshi@CORP.COM

# Show current tickets
klist
# Credentials cache: FILE:/tmp/krb5cc_1001
# Principal: vamshi@CORP.COM
#
# Valid starting     Expires            Service principal
# 04/27/26 01:00:00  04/27/26 11:00:00  krbtgt/CORP.COM@CORP.COM
#   renew until 05/04/26 01:00:00

# Show encryption types used (the -e flag)
klist -e
# 04/27/26 01:00:00  04/27/26 11:00:00  krbtgt/CORP.COM@CORP.COM
#         Etype: aes256-cts-hmac-sha1-96, aes256-cts-hmac-sha1-96

# Get a service ticket for a specific service
kvno host/server.corp.com@CORP.COM
# host/server.corp.com@CORP.COM: kvno = 3

# Show all tickets including service tickets
klist -f
# Flags: F=forwardable, f=forwarded, P=proxiable, p=proxy, D=postdated,
#        d=postdated, R=renewable, I=initial, i=invalid, H=hardware auth

# Destroy all tickets
kdestroy
</code></pre>
<p>The <code class="" data-line="">Valid starting</code> and <code class="" data-line="">Expires</code> fields are the ticket lifetime. After expiry, you need to re-authenticate (or renew the ticket if it&#8217;s within the <code class="" data-line="">renew until</code> window). The <code class="" data-line="">renew until</code> date is when even renewal stops working.</p>
<hr />
<h2 id="etckrb5conf">/etc/krb5.conf</h2>
<pre><code class="" data-line="">[libdefaults]
    default_realm = CORP.COM
    dns_lookup_realm = false
    dns_lookup_kdc = true         # find KDCs via DNS SRV records
    ticket_lifetime = 10h
    renew_lifetime = 7d
    forwardable = true            # tickets can be forwarded to remote hosts (needed for SSH forwarding)
    rdns = false

[realms]
    CORP.COM = {
        kdc = dc01.corp.com
        kdc = dc02.corp.com       # failover KDC
        admin_server = dc01.corp.com
    }

[domain_realm]
    .corp.com = CORP.COM
    corp.com = CORP.COM
</code></pre>
<p>With <code class="" data-line="">dns_lookup_kdc = true</code>, Kerberos finds KDCs by querying DNS SRV records (<code class="" data-line="">_kerberos._tcp.corp.com</code>). AD sets these up automatically. On MIT Kerberos, you add them manually. DNS-based discovery is the recommended approach for AD environments — it picks up new DCs automatically.</p>
<hr />
<h2 id="kerberos-ldap-why-enterprises-run-both">Kerberos + LDAP: Why Enterprises Run Both</h2>
<p>LDAP and Kerberos solve different problems and are almost always deployed together:</p>
<pre><code class="" data-line="">LDAP answers:  &quot;Who is vamshi? What groups is he in? What&#039;s his home directory?&quot;
Kerberos answers: &quot;Is this really vamshi? Prove it without sending a password.&quot;
</code></pre>
<p>Active Directory is exactly this combination — the directory is LDAP-based, the authentication is Kerberos. When a Linux machine joins an AD domain via <code class="" data-line="">realm join</code> or <code class="" data-line="">adcli</code>, it gets:<br />
&#8211; LDAP access to the AD directory (for NSS: user and group lookups)<br />
&#8211; A Kerberos principal registered in AD (for PAM: ticket-based authentication)<br />
&#8211; A machine account (the machine&#8217;s identity in the directory)</p>
<p>When you SSH into an AD-joined Linux machine:<br />
1. SSSD issues a Kerberos AS-REQ for the user&#8217;s TGT<br />
2. SSSD uses the TGT to get a service ticket for the Linux machine&#8217;s PAM service<br />
3. Authentication is verified via the service ticket — no LDAP Bind with a password<br />
4. SSSD does an LDAP Search to get POSIX attributes (UID, GID, home dir)</p>
<p>Password-based LDAP Bind is the fallback when Kerberos isn&#8217;t available. Kerberos is the default on AD-joined systems — and it&#8217;s more secure because the password never leaves the client.</p>
<hr />
<h2 id="common-misconceptions"><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;" /> Common Misconceptions</h2>
<p><strong>&#8220;Kerberos sends your password to the KDC.&#8221;</strong> It doesn&#8217;t. The client derives a key from the password locally and uses that key to encrypt a timestamp (the pre-authentication data). The KDC verifies the timestamp using the stored key. The raw password never travels.</p>
<p><strong>&#8220;Kerberos is an authorization protocol.&#8221;</strong> Kerberos authenticates — it proves who you are. Authorization (what you can do) is a separate decision, usually handled by ACLs on the service or directory group membership.</p>
<p><strong>&#8220;Once you have a TGT, you&#8217;re authenticated to everything.&#8221;</strong> A TGT only proves your identity to the KDC. Each service requires a separate service ticket. The TGT is what lets you get those service tickets without re-entering your password.</p>
<p><strong>&#8220;Kerberos requires AD.&#8221;</strong> MIT Kerberos 5 is a standalone implementation. FreeIPA (EP08) runs MIT Kerberos. Heimdal is another implementation. AD uses a Microsoft-extended version of Kerberos 5, but the core protocol is the same RFC.</p>
<hr />
<h2 id="framework-alignment">Framework Alignment</h2>
<table>
<thead>
<tr>
<th>Domain</th>
<th>Relevance</th>
</tr>
</thead>
<tbody>
<tr>
<td>CISSP Domain 5: Identity and Access Management</td>
<td>Kerberos is the de facto enterprise authentication protocol — SSO, delegation, and service account authentication all depend on it</td>
</tr>
<tr>
<td>CISSP Domain 4: Communications and Network Security</td>
<td>Kerberos prevents credential sniffing and replay attacks — two of the core network authentication threat categories</td>
</tr>
<tr>
<td>CISSP Domain 3: Security Architecture and Engineering</td>
<td>The KDC is a critical single point of trust — its availability, key management, and account (<code class="" data-line="">krbtgt</code>) rotation are architectural security decisions</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="key-takeaways">Key Takeaways</h2>
<ul>
<li>Kerberos is a ticket-based protocol — the password is used once to get a TGT; from then on, tickets prove identity without the password</li>
<li>The three-step flow: get a TGT from the AS, exchange it for a service ticket at the TGS, present the service ticket to the target service</li>
<li>Clock skew over 5 minutes breaks Kerberos — time synchronization is a hard dependency</li>
<li>LDAP stores identity; Kerberos authenticates it — Active Directory is exactly this combination, and so is FreeIPA</li>
<li><code class="" data-line="">klist -e</code> shows the encryption types in use — <code class="" data-line="">aes256-cts-hmac-sha1-96</code> is what you want to see; <code class="" data-line="">arcfour-hmac</code> (RC4) is legacy and should be disabled</li>
</ul>
<hr />
<h2 id="whats-next">What&#8217;s Next</h2>
<p>EP05 covered Kerberos as a protocol. EP06 goes hands-on: building a real LDAP directory with OpenLDAP, configuring replication, and understanding how the server-side components — <code class="" data-line="">slapd</code>, the MDB backend, SyncRepl — fit together.</p>
<p><em>Next: <a href="/openldap-setup-replication/">OpenLDAP Setup and Replication: Running Your Own Directory</a></em></p>
<p>Get EP06 in your inbox when it publishes → <a href="https://linuxcent.com/subscribe">linuxcent.com/subscribe</a></p>
<p><a class="a2a_button_mastodon" href="https://www.addtoany.com/add_to/mastodon?linkurl=https%3A%2F%2Flinuxcent.com%2Fhow-kerberos-works%2F&amp;linkname=How%20Kerberos%20Works%3A%20Tickets%2C%20KDC%2C%20and%20Why%20Enterprises%20Use%20It%20With%20LDAP" 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%2Fhow-kerberos-works%2F&amp;linkname=How%20Kerberos%20Works%3A%20Tickets%2C%20KDC%2C%20and%20Why%20Enterprises%20Use%20It%20With%20LDAP" 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%2Fhow-kerberos-works%2F&amp;linkname=How%20Kerberos%20Works%3A%20Tickets%2C%20KDC%2C%20and%20Why%20Enterprises%20Use%20It%20With%20LDAP" 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%2Fhow-kerberos-works%2F&amp;linkname=How%20Kerberos%20Works%3A%20Tickets%2C%20KDC%2C%20and%20Why%20Enterprises%20Use%20It%20With%20LDAP" 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%2Fhow-kerberos-works%2F&amp;linkname=How%20Kerberos%20Works%3A%20Tickets%2C%20KDC%2C%20and%20Why%20Enterprises%20Use%20It%20With%20LDAP" 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%2Fhow-kerberos-works%2F&amp;linkname=How%20Kerberos%20Works%3A%20Tickets%2C%20KDC%2C%20and%20Why%20Enterprises%20Use%20It%20With%20LDAP" 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%2Fhow-kerberos-works%2F&amp;linkname=How%20Kerberos%20Works%3A%20Tickets%2C%20KDC%2C%20and%20Why%20Enterprises%20Use%20It%20With%20LDAP" 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%2Fhow-kerberos-works%2F&#038;title=How%20Kerberos%20Works%3A%20Tickets%2C%20KDC%2C%20and%20Why%20Enterprises%20Use%20It%20With%20LDAP" data-a2a-url="https://linuxcent.com/how-kerberos-works/" data-a2a-title="How Kerberos Works: Tickets, KDC, and Why Enterprises Use It With LDAP"></a></p><p>The post <a href="https://linuxcent.com/how-kerberos-works/">How Kerberos Works: Tickets, KDC, and Why Enterprises Use It With LDAP</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://linuxcent.com/how-kerberos-works/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1784</post-id>	</item>
		<item>
		<title>LDAP Internals: The Directory Tree, Schema, and What Travels on the Wire</title>
		<link>https://linuxcent.com/ldap-internals-directory-structure/</link>
					<comments>https://linuxcent.com/ldap-internals-directory-structure/#respond</comments>
		
		<dc:creator><![CDATA[Vamshi Krishna Santhapuri]]></dc:creator>
		<pubDate>Fri, 24 Apr 2026 19:41:04 +0000</pubDate>
				<category><![CDATA[Identity & Authentication]]></category>
		<category><![CDATA[Authentication]]></category>
		<category><![CDATA[Directory Services]]></category>
		<category><![CDATA[Identity Management]]></category>
		<category><![CDATA[LDAP]]></category>
		<category><![CDATA[LDAP Schema]]></category>
		<category><![CDATA[ldapsearch]]></category>
		<category><![CDATA[Linux]]></category>
		<guid isPermaLink="false">https://linuxcent.com/ldap-internals-directory-structure/</guid>

					<description><![CDATA[<p><span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 12</span> <span class="rt-label rt-postfix">minutes</span></span>Understand LDAP internals: the DIT hierarchy, DN syntax, object classes, schema, and the BER-encoded bytes that travel from directory server to authentication daemon.</p>
<p>The post <a href="https://linuxcent.com/ldap-internals-directory-structure/">LDAP Internals: The Directory Tree, Schema, and What Travels on the Wire</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></description>
										<content:encoded><![CDATA[<span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 12</span> <span class="rt-label rt-postfix">minutes</span></span><style>
pre{position:relative;background:#1e1e1e;color:#d4d4d4;
    padding:16px 16px 16px 20px;border-radius:6px;overflow-x:auto;
    font-family:'JetBrains Mono','Fira Code','Cascadia Code',Consolas,'Courier New',monospace;
    font-size:.88em;line-height:1.6;border-left:4px solid #555}
code{background:#f4f4f4;padding:2px 5px;border-radius:3px;font-size:.9em}
pre code{background:transparent;padding:0;color:inherit}
pre[data-lang="bash"],pre[data-lang="sh"],
pre[data-lang="shell"],pre[data-lang="zsh"]{border-left-color:#4ec9b0}
pre[data-lang="yaml"],pre[data-lang="json"],
pre[data-lang="toml"],pre[data-lang="xml"]{border-left-color:#569cd6}
pre[data-lang="python"],pre[data-lang="go"],pre[data-lang="rust"],
pre[data-lang="java"],pre[data-lang="c"],pre[data-lang="cpp"]{border-left-color:#c586c0}
pre[data-lang="text"],pre[data-lang="output"],
pre[data-lang="console"]{border-left-color:#888}
.lc-copy-btn{position:absolute;top:8px;right:8px;background:#2d2d2d;color:#ccc;
    border:1px solid #444;border-radius:4px;padding:3px 9px;font-size:.75em;
    font-family:system-ui,sans-serif;cursor:pointer;opacity:0;
    transition:opacity .15s,background .15s;line-height:1.6}
pre:hover .lc-copy-btn{opacity:1}
.lc-copy-btn:hover{background:#3a3a3a;color:#fff}
.lc-copy-btn.copied{color:#4ec9b0;border-color:#4ec9b0}
.lc-lang-badge{position:absolute;top:8px;left:20px;font-family:system-ui,sans-serif;
    font-size:.7em;color:#666;text-transform:uppercase;letter-spacing:.04em;
    line-height:1;pointer-events:none;opacity:0;transition:opacity .15s}
pre:hover .lc-lang-badge{opacity:1}
table{border-collapse:collapse;width:100%;margin:16px 0}
th,td{border:1px solid #ddd;padding:10px 14px;text-align:left}
th{background:#f0f0f0;font-weight:600}
tr:nth-child(even){background:#fafafa}
</style>
<p><script>
(function(){
  if(window.__lcCodeEnhanced)return;
  window.__lcCodeEnhanced=true;
  function enhance(){
    document.querySelectorAll('pre').forEach(function(pre){
      var code=pre.querySelector('code');
      var lang='';
      if(code){var m=(code.className||'').match(/language-(\S+)/);if(m)lang=m[1].toLowerCase();}
      if(lang)pre.setAttribute('data-lang',lang);
      if(lang){var badge=document.createElement('span');badge.className='lc-lang-badge';badge.textContent=lang;pre.insertBefore(badge,pre.firstChild);}
      var btn=document.createElement('button');
      btn.className='lc-copy-btn';btn.textContent='Copy';btn.setAttribute('aria-label','Copy code to clipboard');
      pre.appendChild(btn);
      btn.addEventListener('click',function(){
        var text=code?code.innerText:pre.innerText;
        if(navigator.clipboard&&window.isSecureContext){
          navigator.clipboard.writeText(text).then(function(){ok(btn);}).catch(function(){fb(text,btn);});
        }else{fb(text,btn);}
      });
    });
  }
  function ok(btn){btn.textContent='Copied!';btn.classList.add('copied');setTimeout(function(){btn.textContent='Copy';btn.classList.remove('copied');},2000);}
  function fb(text,btn){
    try{var ta=document.createElement('textarea');ta.value=text;ta.style.cssText='position:fixed;left:-9999px;top:-9999px;opacity:0';document.body.appendChild(ta);ta.select();document.execCommand('copy');document.body.removeChild(ta);ok(btn);}
    catch(e){btn.textContent='✗ Failed';setTimeout(function(){btn.textContent='Copy';},2000);}
  }
  if(document.readyState==='loading'){document.addEventListener('DOMContentLoaded',enhance);}else{enhance();}
})();
</script></p>
<p><em>The Identity Stack, Episode 2</em><br />
<a href="/what-is-ldap/">EP01: What Is LDAP</a> → <strong>EP02</strong> → <a href="/ldap-authentication-linux-pam-nss/">EP03: LDAP Authentication on Linux</a> → &#8230;</p>
<hr />
<h2 id="tldr">TL;DR</h2>
<ul>
<li>The Directory Information Tree (DIT) is the hierarchical database LDAP stores — every entry lives at a unique path described by its Distinguished Name (DN)</li>
<li>Object classes define what attributes an entry is allowed or required to have — <code class="" data-line="">posixAccount</code> adds UID, GID, and home directory; <code class="" data-line="">inetOrgPerson</code> adds email and display name</li>
<li>Schema is the rulebook: which attribute types exist across the entire directory, what syntax each follows, and which object classes require or permit them</li>
<li>An LDAP Search sends four things: a base DN, a scope (base/one/sub), a filter like <code class="" data-line="">(uid=vamshi)</code>, and a list of attributes to return — the server traverses the tree and returns LDIF</li>
<li>Every LDAP message on the wire is BER-encoded (Basic Encoding Rules, a subset of ASN.1) — a compact binary format, not text</li>
<li><code class="" data-line="">ldapsearch</code> output is LDIF (LDAP Data Interchange Format) — the human-readable representation of what the BER payload carried</li>
</ul>
<hr />
<h2 id="the-big-picture-from-ldapsearch-to-directory-entry">The Big Picture: From ldapsearch to Directory Entry</h2>
<pre><code class="" data-line="">ldapsearch -x -H ldap://dc.corp.com -b &quot;dc=corp,dc=com&quot; &quot;(uid=vamshi)&quot; cn mail uidNumber
     │
     │  TCP port 389 (or 636 for LDAPS)
     │  BER-encoded SearchRequest
     ▼
┌─────────────────────────────────────────────────┐
│  LDAP Server (AD / OpenLDAP / 389-DS / FreeIPA)  │
│                                                   │
│  Directory Information Tree                       │
│                                                   │
│  dc=corp,dc=com                    ← search base  │
│    └── ou=engineers                ← scope: sub   │
│          ├── uid=alice                            │
│          └── uid=vamshi  ← filter match           │
│                cn: vamshi                         │
│                mail: vamshi@corp.com              │
│                uidNumber: 1001                    │
└─────────────────────────────────────────────────┘
     │
     │  BER-encoded SearchResultEntry
     ▼
# LDIF output on your terminal
dn: uid=vamshi,ou=engineers,dc=corp,dc=com
cn: vamshi
mail: vamshi@corp.com
uidNumber: 1001
</code></pre>
<p>LDAP internals are the mechanics between the command you type and the directory entry you get back. EP01 explained why LDAP was invented. This episode explains what it actually does when you run it.</p>
<hr />
<h2 id="the-directory-information-tree">The Directory Information Tree</h2>
<p>EP01 introduced the DIT as a concept inherited from X.500. Here&#8217;s what it actually looks like inside a directory.</p>
<p>Every LDAP directory has a root — the base DN — from which all entries descend. For a company called Corp with a domain <code class="" data-line="">corp.com</code>, the base is typically <code class="" data-line="">dc=corp,dc=com</code>. Below that, the tree branches into organizational units, and below those, individual entries for people, groups, services, and anything else the directory administrator decided to model.</p>
<pre><code class="" data-line="">dc=corp,dc=com                          ← domain root (base DN)
│
├── ou=people                           ← organizational unit: people
│     ├── uid=alice                     ← user entry
│     ├── uid=vamshi
│     └── uid=bob
│
├── ou=groups                           ← organizational unit: groups
│     ├── cn=engineers
│     └── cn=ops
│
├── ou=services                         ← organizational unit: service accounts
│     ├── cn=jenkins
│     └── cn=gitlab-runner
│
└── ou=hosts                            ← organizational unit: machines
      ├── cn=web01.corp.com
      └── cn=db01.corp.com
</code></pre>
<p>This hierarchy is not a file system and not a relational database. It is specifically optimized for reads — the query &#8220;give me everything about this user&#8221; is the operation the protocol is built around. Writes are infrequent. Reads are constant.</p>
<p>Every entry in the tree has exactly one parent. There are no cross-links between branches, no foreign keys. The tree is the structure. An entry&#8217;s position in the tree is what defines it.</p>
<hr />
<h2 id="distinguished-names-reading-the-path">Distinguished Names: Reading the Path</h2>
<p>The Distinguished Name (DN) is how you address any entry in the directory. It reads right-to-left, from the leaf to the root, with each component separated by a comma.</p>
<pre><code class="" data-line="">uid=vamshi,ou=engineers,dc=corp,dc=com

Reading right-to-left:
  dc=corp,dc=com       ← domain: corp.com
  ou=engineers         ← organizational unit: engineers
  uid=vamshi           ← this specific entry: user &quot;vamshi&quot;
</code></pre>
<p>Each component of a DN — <code class="" data-line="">uid=vamshi</code>, <code class="" data-line="">ou=engineers</code>, <code class="" data-line="">dc=corp</code> — is a Relative Distinguished Name (RDN). The RDN is the attribute-value pair that uniquely identifies the entry within its parent container. Two users in the same <code class="" data-line="">ou=engineers</code> cannot both have <code class="" data-line="">uid=vamshi</code> — that would create two entries with identical DNs, which the directory won&#8217;t allow.</p>
<p>Common RDN attribute types and what they mean:</p>
<table>
<thead>
<tr>
<th>Attribute</th>
<th>Stands for</th>
<th>Typical use</th>
</tr>
</thead>
<tbody>
<tr>
<td><code class="" data-line="">dc</code></td>
<td>Domain Component</td>
<td>Domain name segments (<code class="" data-line="">dc=corp,dc=com</code> = corp.com)</td>
</tr>
<tr>
<td><code class="" data-line="">ou</code></td>
<td>Organizational Unit</td>
<td>Container for grouping entries</td>
</tr>
<tr>
<td><code class="" data-line="">cn</code></td>
<td>Common Name</td>
<td>Groups, service accounts, human-readable name</td>
</tr>
<tr>
<td><code class="" data-line="">uid</code></td>
<td>User ID</td>
<td>Linux username — the standard RDN for user entries</td>
</tr>
<tr>
<td><code class="" data-line="">o</code></td>
<td>Organization</td>
<td>Top-level org containers (less common in modern setups)</td>
</tr>
</tbody>
</table>
<p>When your Linux system calls <code class="" data-line="">getent passwd vamshi</code>, SSSD translates that into an LDAP Search for an entry where <code class="" data-line="">uid=vamshi</code> somewhere under the configured base DN. The full DN comes back with the result, but what your system cares about are the attributes inside it.</p>
<hr />
<h2 id="object-classes-and-schema">Object Classes and Schema</h2>
<p>Every entry in the directory has a <code class="" data-line="">objectClass</code> attribute — usually several values. Object classes define what attributes the entry is allowed or required to have.</p>
<pre><code class="" data-line=""># A typical user entry&#039;s object classes
dn: uid=vamshi,ou=engineers,dc=corp,dc=com
objectClass: top
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
</code></pre>
<p>Each object class contributes a set of attributes — some required (<code class="" data-line="">MUST</code>), some optional (<code class="" data-line="">MAY</code>):</p>
<pre><code class="" data-line="">objectClass: posixAccount
  MUST: cn, uid, uidNumber, gidNumber, homeDirectory
  MAY:  userPassword, loginShell, gecos, description

objectClass: inetOrgPerson
  MUST: sn (surname), cn
  MAY:  mail, telephoneNumber, displayName, jpegPhoto, ...

objectClass: shadowAccount
  MUST: uid
  MAY:  shadowLastChange, shadowMin, shadowMax, shadowWarning, ...
</code></pre>
<p>When Linux authenticates a user via LDAP, it needs the <code class="" data-line="">posixAccount</code> attributes: <code class="" data-line="">uidNumber</code> (the numeric UID), <code class="" data-line="">gidNumber</code>, <code class="" data-line="">homeDirectory</code>, and <code class="" data-line="">loginShell</code>. Without <code class="" data-line="">posixAccount</code>, the user entry exists in the directory but can&#8217;t be used for Linux logins — <code class="" data-line="">getent passwd</code> will return nothing.</p>
<p>Object classes are grouped into three kinds:</p>
<p>Groups in LDAP use their own object class:</p>
<pre><code class="" data-line="">objectClass: groupOfNames
  MUST: cn, member
  MAY:  description, owner, ...

# A group entry looks like this:
dn: cn=engineers,ou=groups,dc=corp,dc=com
objectClass: groupOfNames
cn: engineers
member: uid=vamshi,ou=engineers,dc=corp,dc=com
member: uid=alice,ou=engineers,dc=corp,dc=com
</code></pre>
<p><code class="" data-line="">groupOfNames</code> stores members as full DNs — which is why the SSSD group search filter is <code class="" data-line="">(member=uid=vamshi,ou=...)</code> rather than <code class="" data-line="">(member=vamshi)</code>. The directory stores the exact path to each member entry. <code class="" data-line="">posixGroup</code> is the alternative, which stores the <code class="" data-line="">memberUid</code> as a bare username string instead of a DN — Active Directory uses <code class="" data-line="">groupOfNames</code>; pure POSIX environments often use <code class="" data-line="">posixGroup</code>.</p>
<p>Object classes are grouped into three kinds:</p>
<p><strong>Structural</strong> — defines what the entry fundamentally is. Every entry must have exactly one structural class. <code class="" data-line="">posixAccount</code> is structural.</p>
<p><strong>Auxiliary</strong> — adds additional attributes to an existing entry. <code class="" data-line="">shadowAccount</code> and <code class="" data-line="">inetOrgPerson</code> can be auxiliary. You can stack multiple auxiliary classes on a single entry.</p>
<p><strong>Abstract</strong> — base classes that other classes inherit from. <code class="" data-line="">top</code> is the root abstract class that every entry implicitly has. You never add <code class="" data-line="">top</code> to an entry; it&#8217;s always there.</p>
<h3 id="schema-the-directorys-type-system">Schema: The Directory&#8217;s Type System</h3>
<p>Schema is the global rulebook for the entire directory. It defines:</p>
<ul>
<li><strong>Attribute type definitions</strong> — what each attribute is named, what syntax it uses (a string? an integer? a binary blob?), whether it&#8217;s case-sensitive, whether multiple values are allowed</li>
<li><strong>Object class definitions</strong> — which attributes each class requires or permits</li>
<li><strong>Matching rules</strong> — how equality comparisons work for each attribute type</li>
</ul>
<p>The schema is stored in the directory itself, under a special entry at <code class="" data-line="">cn=schema,cn=config</code> (OpenLDAP) or <code class="" data-line="">cn=Schema,cn=Configuration</code> (Active Directory). You can query it:</p>
<pre><code class="" data-line=""># View the schema for the posixAccount object class
ldapsearch -x -H ldap://your-dc \
  -b &quot;cn=schema,cn=config&quot; \
  &quot;(objectClass=olcObjectClasses)&quot; \
  olcObjectClasses | grep -A 10 &quot;posixAccount&quot;

# Output:
# olcObjectClasses: ( 1.3.6.1.1.1.2.0
#   NAME &#039;posixAccount&#039;
#   DESC &#039;Abstraction of an account with POSIX attributes&#039;
#   SUP top
#   AUXILIARY
#   MUST ( cn $ uid $ uidNumber $ gidNumber $ homeDirectory )
#   MAY ( userPassword $ loginShell $ gecos $ description ) )
</code></pre>
<p>That OID (<code class="" data-line="">1.3.6.1.1.1.2.0</code>) is the globally unique identifier for the <code class="" data-line="">posixAccount</code> object class. Every object class and attribute type in every LDAP directory on the planet has a unique OID assigned by an authority. This is how schema interoperability works across different directory implementations — OpenLDAP, Active Directory, and 389-DS can all understand each other&#8217;s <code class="" data-line="">posixAccount</code> entries because they share the same OID.</p>
<hr />
<h2 id="ldap-operations-what-actually-runs">LDAP Operations: What Actually Runs</h2>
<p>LDAP defines eight operations. Day-to-day authentication uses two: Bind and Search.</p>
<pre><code class="" data-line="">LDAP Operation Set
──────────────────
Bind        ← authenticate (prove identity)
Search      ← query the directory
Add         ← create a new entry
Modify      ← change attributes on an existing entry
Delete      ← remove an entry
ModifyDN    ← rename or move an entry
Compare     ← test if an attribute has a specific value
Abandon     ← cancel an outstanding operation
</code></pre>
<h3 id="bind-proving-who-you-are">Bind: Proving Who You Are</h3>
<p>Before any authenticated operation, the client sends a Bind request. There are two types:</p>
<p><strong>Simple Bind</strong> — the client sends its DN and password in the clear (or over TLS). This is what <code class="" data-line="">-x</code> in <code class="" data-line="">ldapsearch</code> means: simple authentication.</p>
<pre><code class="" data-line=""># Simple bind as a service account
ldapsearch -x \
  -D &quot;cn=svc-ldap-reader,ou=services,dc=corp,dc=com&quot; \
  -w &quot;service-account-password&quot; \
  -H ldap://dc.corp.com \
  -b &quot;dc=corp,dc=com&quot; \
  &quot;(uid=vamshi)&quot;
</code></pre>
<p><strong>SASL Bind</strong> — the client uses an authentication mechanism registered with SASL (Simple Authentication and Security Layer). Kerberos (via the GSSAPI mechanism) is the most common. EP05 covers Kerberos in detail.</p>
<pre><code class="" data-line=""># SASL bind using Kerberos (after kinit)
ldapsearch -Y GSSAPI \
  -H ldap://dc.corp.com \
  -b &quot;dc=corp,dc=com&quot; \
  &quot;(uid=vamshi)&quot;
</code></pre>
<p>An anonymous Bind (no DN, no password) is also valid for directories configured to allow anonymous reads. Many public LDAP directories (and some internal ones, misconfigured) allow this.</p>
<h3 id="search-the-core-operation">Search: The Core Operation</h3>
<p>A Search request has five required parameters:</p>
<pre><code class="" data-line="">baseObject   — where in the DIT to start (e.g., &quot;dc=corp,dc=com&quot;)
scope        — how deep to look
               base    = only the base entry itself
               one     = one level below base (immediate children)
               sub     = entire subtree below base (most common)
derefAliases — how to handle alias entries (usually derefAlways)
filter       — what to match (e.g., &quot;(uid=vamshi)&quot;)
attributes   — which attributes to return (empty = return all)
</code></pre>
<p>When SSSD authenticates a user login, it runs exactly two Search operations:</p>
<pre><code class="" data-line="">Search 1 — find the user&#039;s entry
  base:       dc=corp,dc=com
  scope:      sub
  filter:     (uid=vamshi)
  attributes: dn, uid, uidNumber, gidNumber, homeDirectory, loginShell

Search 2 — find the user&#039;s group memberships
  base:       dc=corp,dc=com
  scope:      sub
  filter:     (member=uid=vamshi,ou=engineers,dc=corp,dc=com)
  attributes: dn, cn, gidNumber
</code></pre>
<p>The first search locates the user entry and retrieves the POSIX attributes. The second finds all group entries that contain the user&#8217;s DN as a member. These two queries are the complete basis for a Linux login over LDAP.</p>
<h3 id="search-filters">Search Filters</h3>
<p>LDAP filters follow a prefix (Polish notation) syntax. Every filter is wrapped in parentheses:</p>
<pre><code class="" data-line=""># Simple equality
(uid=vamshi)

# Presence — entry has this attribute at all
(mail=*)

# Substring match
(cn=vam*)

# Comparison
(uidNumber&gt;=1000)

# Logical AND — both conditions must match
(&amp;(objectClass=posixAccount)(uid=vamshi))

# Logical OR — either condition matches
(|(uid=vamshi)(mail=vamshi@corp.com))

# Logical NOT
(!(uid=guest))

# Combined — posixAccount entries with UID &gt;= 1000 and no disabled flag
(&amp;(objectClass=posixAccount)(uidNumber&gt;=1000)(!(pwdAccountLockedTime=*)))
</code></pre>
<p>The <code class="" data-line="">&amp;</code> and <code class="" data-line="">|</code> operators take any number of operands. Filter syntax looks strange the first time but is unambiguous and compact — which matters when you&#8217;re encoding it into BER for the wire.</p>
<hr />
<h2 id="what-actually-travels-on-the-wire">What Actually Travels on the Wire</h2>
<p>Every LDAP message is encoded in BER (Basic Encoding Rules), a binary subset of ASN.1. LDAP is not a text protocol.</p>
<p>When you run <code class="" data-line="">ldapsearch</code>, the tool constructs a BER-encoded <code class="" data-line="">SearchRequest</code> message and sends it over TCP. The server responds with one or more <code class="" data-line="">SearchResultEntry</code> messages (one per matching entry), followed by a <code class="" data-line="">SearchResultDone</code>. All of these are BER.</p>
<p>BER uses a type-length-value (TLV) encoding:</p>
<pre><code class="" data-line="">Tag byte(s)    — what type of data this is
Length byte(s) — how many bytes of data follow
Value byte(s)  — the actual data
</code></pre>
<p>A minimal LDAP SearchRequest for <code class="" data-line="">ldapsearch -x -b &quot;dc=corp,dc=com&quot; &quot;(uid=vamshi)&quot; uid</code> looks like this on the wire:</p>
<pre><code class="" data-line="">30 45          ← SEQUENCE (LDAPMessage)
  02 01 01     ← INTEGER 1 (messageID = 1)
  63 40        ← [APPLICATION 3] SearchRequest
    04 11       ← OCTET STRING: baseObject
      64 63 3d  ← &quot;dc=corp,dc=com&quot; (20 bytes)
      63 6f 72
      70 2c 64
      63 3d 63
      6f 6d
    0a 01 02   ← ENUMERATED: scope = wholeSubtree (2)
    0a 01 03   ← ENUMERATED: derefAliases = derefAlways (3)
    02 01 00   ← INTEGER: sizeLimit = 0 (unlimited)
    02 01 00   ← INTEGER: timeLimit = 0 (unlimited)
    01 01 00   ← BOOLEAN: typesOnly = false
    a7 0f      ← [7] equalityMatch filter
      04 03 75 69 64   ← attributeDesc: &quot;uid&quot;
      04 06 76 61 6d   ← assertionValue: &quot;vamshi&quot;
             73 68 69
    30 05      ← SEQUENCE: AttributeDescriptionList
      04 03 75 69 64   ← &quot;uid&quot;
</code></pre>
<p>You don&#8217;t need to read BER by hand in practice. But knowing it&#8217;s binary — not HTTP, not JSON, not plain text — explains some things:</p>
<ul>
<li>Why <code class="" data-line="">tcpdump port 389</code> shows binary output you can&#8217;t read directly</li>
<li>Why LDAP on port 389 looks different in Wireshark than HTTP traffic</li>
<li>Why <code class="" data-line="">ldapsearch</code> output (LDIF) is a transformation of the wire data, not the wire data itself</li>
</ul>
<p>To see the wire protocol in action:</p>
<pre><code class="" data-line=""># Run ldapsearch with debug output (level 1 = protocol tracing)
ldapsearch -d 1 -x \
  -H ldap://ldap.forumsys.com \
  -b &quot;dc=example,dc=com&quot; \
  -D &quot;cn=read-only-admin,dc=example,dc=com&quot; \
  -w readonly \
  &quot;(uid=tesla)&quot; cn

# You&#039;ll see output like:
# ldap_connect_to_host: TCP ldap.forumsys.com:389
# ldap_new_connection 1 1 0
# ldap_connect_to_host: Trying ldap.forumsys.com:389
# ldap_pvt_connect: fd: 5 tm: -1 async: 0
# TLS: can&#039;t connect.
# ldap_open_defconn: successful
# ber_scanf fmt ({it) ber:     ← BER decoding of the response
# ber_scanf fmt ({) ber:
# ber_scanf fmt (W) ber:
# ...
</code></pre>
<p>The <code class="" data-line="">ber_scanf</code> lines are the BER decoder working through the server&#8217;s response. Each line represents one TLV element being read off the wire.</p>
<hr />
<h2 id="reading-ldapsearch-output-every-field">Reading ldapsearch Output: Every Field</h2>
<p><code class="" data-line="">ldapsearch</code> output is LDIF (LDAP Data Interchange Format), defined in RFC 2849. It&#8217;s the standard text serialization of LDAP entries.</p>
<pre><code class="" data-line="">ldapsearch -x \
  -H ldap://ldap.forumsys.com \
  -b &quot;dc=example,dc=com&quot; \
  -D &quot;cn=read-only-admin,dc=example,dc=com&quot; \
  -w readonly \
  &quot;(uid=tesla)&quot; \
  cn mail uid uidNumber objectClass
</code></pre>
<p>Output, annotated:</p>
<pre><code class="" data-line=""># extended LDIF
#
# LDAPv3                              ← protocol version confirmed
# base &lt;dc=example,dc=com&gt; with scope subtree
# filter: (uid=tesla)                 ← your search filter echoed back
# requesting: cn mail uid uidNumber objectClass
#

# tesla, example.com                  ← comment: CN, base DN
dn: uid=tesla,dc=example,dc=com      ← Distinguished Name — full path in the tree

objectClass: inetOrgPerson           ← structural class: person with org attrs
objectClass: organizationalPerson    ← auxiliary: adds telephoneNumber etc.
objectClass: person                  ← auxiliary: adds sn (surname)
objectClass: top                     ← every entry has this implicitly
cn: Tesla                            ← common name (from inetOrgPerson MUST)
mail: tesla@ldap.forumsys.com        ← email (from inetOrgPerson MAY)
uid: tesla                           ← userid (from inetOrgPerson MAY)

# search result
search: 2                            ← messageID of the SearchResultDone
result: 0 Success                    ← 0 = no error; 32 = no such object; 49 = invalid credentials

# numResponses: 2                    ← 1 result entry + 1 SearchResultDone
# numEntries: 1
</code></pre>
<p>The <code class="" data-line="">result:</code> line is the one to watch when debugging. LDAP result codes:</p>
<table>
<thead>
<tr>
<th>Code</th>
<th>Meaning</th>
<th>What it tells you</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>Success</td>
<td>Query ran, results returned (or no results found — check numEntries)</td>
</tr>
<tr>
<td>32</td>
<td>No Such Object</td>
<td>Base DN doesn&#8217;t exist in this directory</td>
</tr>
<tr>
<td>49</td>
<td>Invalid Credentials</td>
<td>Bind failed — wrong DN, wrong password, or account locked</td>
</tr>
<tr>
<td>50</td>
<td>Insufficient Access</td>
<td>Your bind DN doesn&#8217;t have read permission on these entries</td>
</tr>
<tr>
<td>53</td>
<td>Unwilling to Perform</td>
<td>Server refused the operation (e.g., password policy, anonymous bind disabled)</td>
</tr>
<tr>
<td>65</td>
<td>Object Class Violation</td>
<td>Add/Modify would violate schema (missing MUST attribute, unrecognized object class)</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="ports-389-636-and-3268">Ports: 389, 636, and 3268</h2>
<pre><code class="" data-line="">Port 389   — LDAP (plaintext, or StartTLS in-session upgrade)
Port 636   — LDAPS (LDAP wrapped in TLS from the start)
Port 3268  — Active Directory Global Catalog (plain)
Port 3269  — Active Directory Global Catalog over TLS
</code></pre>
<p><strong>Port 389 vs 636:</strong> Both carry the same BER-encoded LDAP protocol. The difference is when TLS starts. On 636 (LDAPS), the TLS handshake happens before the first LDAP message. On 389 with StartTLS, the client sends a plaintext <code class="" data-line="">ExtendedRequest</code> with OID <code class="" data-line="">1.3.6.1.4.1.1466.20037</code> to initiate the TLS upgrade, then both sides continue over TLS. In production, use one or the other — never unencrypted port 389. Your credentials transit the wire on every Bind.</p>
<p><strong>Ports 3268/3269 — Active Directory Global Catalog:</strong> AD organizes domains into forests. Each domain controller holds the full LDAP tree for its own domain. The Global Catalog is a read-only, partial replica of every domain in the forest — just the most-queried attributes from every object. When an application needs to find a user across domains in the same forest (not just in one domain), it queries the Global Catalog on 3268/3269 instead of a domain-specific DC on 389/636.</p>
<pre><code class="" data-line="">Forest: corp.com
  ├── Domain: corp.com       → DC at port 389/636   (full copy of corp.com)
  ├── Domain: emea.corp.com  → DC at port 389/636   (full copy of emea.corp.com)
  └── Global Catalog        → GC at port 3268/3269  (partial copy of ALL domains)
</code></pre>
<p>If your SSSD or application is configured to use port 3268 instead of 389, it&#8217;s talking to the Global Catalog — useful for forest-wide user lookups, but missing some less-common attributes that aren&#8217;t replicated to the GC.</p>
<hr />
<h2 id="try-it-ldapsearch-against-your-own-directory">Try It: ldapsearch Against Your Own Directory</h2>
<p>If your Linux machine is joined to AD or connected to an LDAP directory, you can run these right now:</p>
<pre><code class="" data-line=""># 1. Confirm your SSSD knows where the LDAP server is
grep -E &quot;ldap_uri|ad_domain|krb5_server&quot; /etc/sssd/sssd.conf

# 2. Look up your own user entry
ldapsearch -x \
  -H ldap://$(grep ldap_uri /etc/sssd/sssd.conf | awk -F= &#039;{print $2}&#039; | tr -d &#039; &#039;) \
  -b &quot;dc=$(hostname -d | sed &#039;s/\./,dc=/g&#039;)&quot; \
  &quot;(uid=$(whoami))&quot; \
  dn objectClass uid uidNumber gidNumber homeDirectory loginShell

# 3. Find the groups you&#039;re in
ldapsearch -x \
  -H ldap://your-dc \
  -b &quot;dc=corp,dc=com&quot; \
  &quot;(member=$(ldapsearch -x ... &quot;(uid=$(whoami))&quot; dn | grep ^dn | cut -d&#039; &#039; -f2-))&quot; \
  cn gidNumber

# 4. Check what object classes your entry has
ldapsearch -x \
  -H ldap://your-dc \
  -b &quot;dc=corp,dc=com&quot; \
  &quot;(uid=$(whoami))&quot; \
  objectClass
</code></pre>
<p>On a machine joined to Active Directory, the <code class="" data-line="">ldap_uri</code> in sssd.conf is your domain controller&#8217;s address. On FreeIPA or OpenLDAP, it&#8217;s the directory server. The same <code class="" data-line="">ldapsearch</code> commands work against all of them — because they all speak LDAP v3.</p>
<hr />
<h2 id="common-misconceptions"><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;" /> Common Misconceptions</h2>
<p><strong>&#8220;The DN is like a file path.&#8221;</strong> The analogy holds for reading it, but the DIT is not a file system. Entries don&#8217;t inherit permissions from parent containers the way files inherit from directories. Access control in LDAP is defined by ACLs on the server — not by position in the tree.</p>
<p><strong>&#8220;LDAP is case-sensitive.&#8221;</strong> It depends on the attribute. Most string attributes (like <code class="" data-line="">cn</code> and <code class="" data-line="">mail</code>) use case-insensitive matching by default — <code class="" data-line="">(cn=Vamshi)</code> and <code class="" data-line="">(cn=vamshi)</code> return the same results. But some attributes (like <code class="" data-line="">userPassword</code> and most binary types) are case-sensitive. The schema&#8217;s matching rules define this per-attribute.</p>
<p><strong>&#8220;You need the full DN to search for a user.&#8221;</strong> No. The <code class="" data-line="">Search</code> operation with a <code class="" data-line="">sub</code> scope searches the entire subtree below the base DN. You search with a filter like <code class="" data-line="">(uid=vamshi)</code> without knowing the full DN. The DN comes back in the result.</p>
<p><strong>&#8220;LDAP accounts and Linux accounts are the same thing.&#8221;</strong> An LDAP user entry becomes a Linux account only if the entry has a <code class="" data-line="">posixAccount</code> object class with the required POSIX attributes (<code class="" data-line="">uidNumber</code>, <code class="" data-line="">gidNumber</code>, <code class="" data-line="">homeDirectory</code>). An LDAP entry without <code class="" data-line="">posixAccount</code> can exist in the directory but <code class="" data-line="">getent passwd</code> will not return it.</p>
<p><strong>&#8220;The objectClass attribute can be changed freely.&#8221;</strong> Structural object classes cannot be changed after an entry is created — you&#8217;d have to delete and recreate the entry. Auxiliary classes can be added or removed. This is why correctly choosing the structural class at entry creation time matters.</p>
<hr />
<h2 id="framework-alignment">Framework Alignment</h2>
<table>
<thead>
<tr>
<th>Domain</th>
<th>Relevance</th>
</tr>
</thead>
<tbody>
<tr>
<td>CISSP Domain 5: Identity and Access Management</td>
<td>DIT structure, DN addressing, object classes, and schema are the data model underpinning every enterprise identity store — understanding them is foundational to managing directory-based IAM</td>
</tr>
<tr>
<td>CISSP Domain 4: Communications and Network Security</td>
<td>BER on port 389 is unencrypted; LDAPS (port 636) or StartTLS is required for production — wire-level understanding informs the transport security decision</td>
</tr>
<tr>
<td>CISSP Domain 3: Security Architecture and Engineering</td>
<td>Schema design and DIT hierarchy are architectural decisions with security consequences: overly permissive schemas enable privilege escalation; flat DITs make access delegation harder</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="key-takeaways">Key Takeaways</h2>
<ul>
<li>The DIT is a hierarchical database — every entry has a unique DN that describes its path from leaf to root</li>
<li>Object classes define the schema rules for each entry: what attributes are required (<code class="" data-line="">MUST</code>) vs optional (<code class="" data-line="">MAY</code>), and what the entry fundamentally is</li>
<li>For a user to be usable for Linux logins, the directory entry needs the <code class="" data-line="">posixAccount</code> object class with <code class="" data-line="">uidNumber</code>, <code class="" data-line="">gidNumber</code>, and <code class="" data-line="">homeDirectory</code> populated</li>
<li>An LDAP login is two operations: a Bind (authenticate), then a Search (retrieve POSIX attributes and group memberships)</li>
<li>Everything on the wire is BER-encoded binary — <code class="" data-line="">ldapsearch</code> output is LDIF, a human-readable transformation of what the wire actually carries</li>
<li>LDAP result code 0 means success; 49 means bad credentials; 32 means the base DN doesn&#8217;t exist — these are the three you&#8217;ll debug most often</li>
</ul>
<hr />
<hr />
<p>Run <code class="" data-line="">ldapsearch</code> against your own directory and look at the object classes on your entry. Does it have <code class="" data-line="">posixAccount</code>? Does it have <code class="" data-line="">shadowAccount</code>? What attributes is your SSSD actually reading on every login — and what does it do when the LDAP server is unreachable? <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f447.png" alt="👇" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>
<hr />
<h2 id="whats-next">What&#8217;s Next</h2>
<p>EP02 showed what&#8217;s inside the directory: the tree structure, the schema, the operations, and the wire protocol. What it left open is how Linux actually uses this information to grant a login.</p>
<p>LDAP is not, by itself, an authentication protocol. The Bind operation can verify a password — but that&#8217;s a tiny piece of what happens when you SSH into a machine joined to Active Directory. The full login flow runs through PAM, NSS, and SSSD before LDAP ever gets queried. EP03 traces that path.</p>
<p><em>Next: <a href="/ldap-authentication-linux-pam-nss/">LDAP Authentication on Linux: PAM, NSS, and the Login Stack</a></em></p>
<p>Get EP03 in your inbox when it publishes → <a href="https://linuxcent.com/subscribe">linuxcent.com/subscribe</a></p>
<p><a class="a2a_button_mastodon" href="https://www.addtoany.com/add_to/mastodon?linkurl=https%3A%2F%2Flinuxcent.com%2Fldap-internals-directory-structure%2F&amp;linkname=LDAP%20Internals%3A%20The%20Directory%20Tree%2C%20Schema%2C%20and%20What%20Travels%20on%20the%20Wire" 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%2Fldap-internals-directory-structure%2F&amp;linkname=LDAP%20Internals%3A%20The%20Directory%20Tree%2C%20Schema%2C%20and%20What%20Travels%20on%20the%20Wire" 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%2Fldap-internals-directory-structure%2F&amp;linkname=LDAP%20Internals%3A%20The%20Directory%20Tree%2C%20Schema%2C%20and%20What%20Travels%20on%20the%20Wire" 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%2Fldap-internals-directory-structure%2F&amp;linkname=LDAP%20Internals%3A%20The%20Directory%20Tree%2C%20Schema%2C%20and%20What%20Travels%20on%20the%20Wire" 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%2Fldap-internals-directory-structure%2F&amp;linkname=LDAP%20Internals%3A%20The%20Directory%20Tree%2C%20Schema%2C%20and%20What%20Travels%20on%20the%20Wire" 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%2Fldap-internals-directory-structure%2F&amp;linkname=LDAP%20Internals%3A%20The%20Directory%20Tree%2C%20Schema%2C%20and%20What%20Travels%20on%20the%20Wire" 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%2Fldap-internals-directory-structure%2F&amp;linkname=LDAP%20Internals%3A%20The%20Directory%20Tree%2C%20Schema%2C%20and%20What%20Travels%20on%20the%20Wire" 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%2Fldap-internals-directory-structure%2F&#038;title=LDAP%20Internals%3A%20The%20Directory%20Tree%2C%20Schema%2C%20and%20What%20Travels%20on%20the%20Wire" data-a2a-url="https://linuxcent.com/ldap-internals-directory-structure/" data-a2a-title="LDAP Internals: The Directory Tree, Schema, and What Travels on the Wire"></a></p><p>The post <a href="https://linuxcent.com/ldap-internals-directory-structure/">LDAP Internals: The Directory Tree, Schema, and What Travels on the Wire</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://linuxcent.com/ldap-internals-directory-structure/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1771</post-id>	</item>
		<item>
		<title>What Is LDAP — and Why It Was Invented to Replace Something Worse</title>
		<link>https://linuxcent.com/what-is-ldap/</link>
					<comments>https://linuxcent.com/what-is-ldap/#respond</comments>
		
		<dc:creator><![CDATA[Vamshi Krishna Santhapuri]]></dc:creator>
		<pubDate>Fri, 24 Apr 2026 04:43:19 +0000</pubDate>
				<category><![CDATA[Identity & Authentication]]></category>
		<category><![CDATA[Authentication]]></category>
		<category><![CDATA[Directory Services]]></category>
		<category><![CDATA[Enterprise IT]]></category>
		<category><![CDATA[Identity Management]]></category>
		<category><![CDATA[LDAP]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[linux-security]]></category>
		<guid isPermaLink="false">https://linuxcent.com/what-is-ldap/</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"> 9</span> <span class="rt-label rt-postfix">minutes</span></span>LDAP solved 1980s authentication chaos and still powers enterprise logins today. Learn what it replaced, how it works, and why it's still in your stack.</p>
<p>The post <a href="https://linuxcent.com/what-is-ldap/">What Is LDAP — and Why It Was Invented to Replace Something Worse</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"> 9</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>
<p><em>The Identity Stack, Episode 1</em><br />
<strong>EP01</strong> → <a href="/ldap-internals-directory-structure/">EP02: LDAP Internals</a> → EP03 → &#8230;</p>
<hr />
<h2 id="tldr">TL;DR</h2>
<ul>
<li>LDAP (Lightweight Directory Access Protocol) is a protocol for reading and writing directory information — most commonly, who is allowed to do what</li>
<li>It was built in 1993 as a &#8220;lightweight&#8221; alternative to X.500/DAP, which ran over the full OSI stack and was impossible to deploy on anything but mainframe hardware</li>
<li>Before LDAP, every server had its own <code class="" data-line="">/etc/passwd</code> — 50 machines meant 50 separate user databases, managed manually</li>
<li>NIS (Network Information Service) was the first attempt to centralize this — it worked, then became a cleartext-credentials security liability</li>
<li>LDAP v3 (RFC 2251, 1997) is the version still in production today — 27 years of backwards compatibility</li>
<li>Everything you use today — Active Directory, Okta, Entra ID — is built on top of, or speaks, LDAP</li>
</ul>
<hr />
<h2 id="the-big-picture-50-years-of-who-are-you">The Big Picture: 50 Years of &#8220;Who Are You?&#8221;</h2>
<pre><code class="" data-line="">1969–1980s   /etc/passwd — per-machine, no network auth
     │        50 servers = 50 user databases, managed manually
     │
     ▼
1984         Sun NIS / Yellow Pages — first centralized directory
     │        broadcast-based, no encryption, flat namespace
     │        Revolutionary for its era. A liability by the 1990s.
     │
     ▼
1988         X.500 / DAP — enterprise-grade directory services
     │        OSI protocol stack. Powerful. Impossible to deploy.
     │        Mainframe-class infrastructure required just to run it.
     │
     ▼
1993         RFC 1487 — LDAP v1
     │        Tim Howes, University of Michigan.
     │        Lightweight. TCP/IP. Actually deployable.
     │
     ▼
1997         RFC 2251 — LDAP v3
     │        SASL authentication. TLS. Controls. Referrals.
     │        The version still in production today.
     │
     ▼
2000s–now    Active Directory, OpenLDAP, 389-DS, FreeIPA
             Okta, Entra ID, Google Workspace
             LDAP DNA in every identity system on the planet.
</code></pre>
<p>What is LDAP? It&#8217;s the protocol that solved one of the most boring and consequential problems in computing: how do you know who someone is, across machines, at scale, without sending their password in cleartext?</p>
<hr />
<h2 id="the-world-before-ldap">The World Before LDAP</h2>
<p>Before you understand why LDAP was invented, you need to feel the problem it solved.</p>
<p>Every Unix machine in the 1970s and 1980s managed its own users. When you created an account on a server, your username, UID, and hashed password went into <code class="" data-line="">/etc/passwd</code> on that machine. Another machine had no idea you existed. If you needed access to ten servers, an administrator created ten separate accounts — manually, one by one. When you changed your password, each account had to be updated separately.</p>
<p>For a university with 200 machines and 10,000 students, this was chaos. For a company with offices in three cities, it was a full-time job for multiple sysadmins.</p>
<pre><code class="" data-line="">Machine A           Machine B           Machine C
/etc/passwd         /etc/passwd         /etc/passwd
vamshi:x:1001       (vamshi unknown)    vamshi:x:1004
alice:x:1002        alice:x:1001        alice:x:1003
bob:x:1003          bob:x:1002          (bob unknown)

Same people, different UIDs, different machines, no central truth.
File permissions become meaningless when UID 1001 means
different users on different hosts.
</code></pre>
<p>For every new hire, an admin SSHed to every machine and ran <code class="" data-line="">useradd</code>. When someone left, you hoped whoever ran the offboarding remembered all the machines. Most organizations didn&#8217;t know their own attack surface because there was no single place to look.</p>
<hr />
<h3 id="sun-nis-the-first-attempt-at-centralization">Sun NIS: The First Attempt at Centralization</h3>
<p>Sun Microsystems released NIS (Network Information Service) in 1984, originally called Yellow Pages — a name they had to drop after a trademark dispute with British Telecom. The idea was elegant: one server holds the authoritative <code class="" data-line="">/etc/passwd</code> (and <code class="" data-line="">/etc/group</code>, <code class="" data-line="">/etc/hosts</code>, and a dozen other maps), and client machines query it instead of reading local files.</p>
<p>For the first time, you could create an account once and have it work across your entire network. For a generation of Unix administrators, NIS was liberating.</p>
<pre><code class="" data-line="">       NIS Master Server
       /var/yp/passwd.byname
              │
    ┌─────────┼──────────┐
    ▼         ▼          ▼
 Client A   Client B   Client C
 (query NIS — no local /etc/passwd needed)
</code></pre>
<p>NIS worked well — until it didn&#8217;t. The failure modes were structural:</p>
<p><strong>No encryption.</strong> NIS responses were cleartext UDP. An attacker on the same network segment could capture the full password database with a packet sniffer. In 1984, &#8220;the network&#8221; meant a trusted corporate LAN. By the mid-1990s, it meant ethernet segments that included lab workstations, and the assumptions no longer held.</p>
<p><strong>Broadcast-based discovery.</strong> NIS clients found servers by broadcasting on the local network. This worked on a single flat ethernet. It failed completely across routers, across buildings, and across WAN links. Multi-site organizations ended up running separate NIS domains with no connection between them — which partially defeated the purpose.</p>
<p><strong>Flat namespace.</strong> NIS had no organizational hierarchy. One domain. Everything flat. You couldn&#8217;t have <code class="" data-line="">engineering</code> and <code class="" data-line="">finance</code> as separate administrative units. You couldn&#8217;t delegate user management to a department. One person — usually one overworked sysadmin — managed the whole thing.</p>
<p><strong>UIDs had to match across all machines.</strong> If <code class="" data-line="">alice</code> was UID 1002 on one server but UID 1001 on another, NFS file ownership became wrong. NIS enforced consistency, but onboarding a new machine into an existing network required manually auditing UID conflicts across the entire directory. Get one wrong and files end up owned by the wrong person.</p>
<p>NIS worked for thousands of installations from 1984 to the mid-1990s. It also ended careers when it failed. What the industry needed was a hierarchical, structured, encrypted, scalable directory service.</p>
<hr />
<h2 id="x500-and-dap-the-right-idea-wrong-protocol">X.500 and DAP: The Right Idea, Wrong Protocol</h2>
<p>The OSI (Open Systems Interconnection) standards body had an answer: X.500 directory services. X.500 was comprehensive, hierarchical, globally federated. The ITU-T published the standard in 1988, and it looked like exactly what enterprises needed.</p>
<pre><code class="" data-line="">X.500 Directory Information Tree (DIT)
              c=US                   ← country
                │
         o=University                ← organization
                │
         ┌──────┴──────┐
     ou=CS           ou=Physics      ← organizational units
         │
     cn=Tim Howes                    ← common name (person)
     telephoneNumber: +1-734-...
     mail: tim@umich.edu
</code></pre>
<p>This data model — the hierarchy, the object classes, the distinguished names — is exactly what LDAP inherited. The DIT, the <code class="" data-line="">cn=</code>, <code class="" data-line="">ou=</code>, <code class="" data-line="">dc=</code> notation in every LDAP query you&#8217;ve ever read: all of it came from X.500.</p>
<p>The problem was DAP: the Directory Access Protocol that X.500 used to communicate.</p>
<p>DAP ran over the full OSI protocol stack. Not TCP/IP — OSI. Seven layers, all of which required specialized software that in 1988 only mainframe and minicomputer vendors had implemented. A university department wanting to run X.500 needed hardware and software licenses that cost as much as a small car. The vast majority of workstations couldn&#8217;t speak OSI at all.</p>
<p>The data model was sound. The transport was impractical.</p>
<pre><code class="" data-line="">X.500 / DAP (1988)              LDAP v1 (1993)
──────────────────              ──────────────
Full OSI stack (7 layers)  →    TCP/IP only
Mainframe-class hardware   →    Any Unix box with a TCP stack
$50,000+ deployment cost   →    Free (reference implementation)
Vendor-specific OSI impl.  →    Standard socket API
Zero internet adoption     →    Universities deployed immediately
</code></pre>
<hr />
<h2 id="the-invention-ldap-at-the-university-of-michigan">The Invention: LDAP at the University of Michigan</h2>
<p>Tim Howes was at the University of Michigan in the early 1990s. The university was running X.500 for its directory — faculty, staff, student contact information, credentials. The data model was good. The protocol was the problem.</p>
<p>His insight, working with colleagues Wengyik Yeong and Steve Kille: strip X.500 down to what actually needs to function over a TCP/IP connection. Keep the hierarchical data model. Throw away the OSI transport. The result was the Lightweight Directory Access Protocol.</p>
<p>RFC 1487, published July 1993, described LDAP v1. It preserved the X.500 directory information model — the hierarchy, the object classes, the distinguished name format — and mapped it onto a protocol that could run over a simple TCP socket on port 389.</p>
<p>No specialized hardware. No OSI. If you had a Unix machine and TCP/IP, you could run LDAP. By 1993, that meant virtually every workstation and server in every university and most enterprises.</p>
<p>The University of Michigan deployed it immediately. Within two years, organizations across the internet were running the reference implementation.</p>
<p>LDAP v2 (RFC 1777, 1995) cleaned up the protocol. LDAP v3 (RFC 2251, 1997) is the version in production today — adding SASL authentication (which enables Kerberos integration), TLS support, referrals for federated directories, and extensible controls for server-side operations. The RFC that standardized the internet&#8217;s primary identity protocol is 27 years old and still running.</p>
<hr />
<h2 id="what-ldap-actually-is">What LDAP Actually Is</h2>
<p>LDAP is a client-server protocol for reading and writing a directory — a structured, hierarchical database optimized for reads.</p>
<p>Every entry in the directory has a Distinguished Name (DN) that describes its position in the hierarchy, and a set of attributes defined by its object classes. A person entry looks like this:</p>
<pre><code class="" data-line="">dn: cn=vamshi,ou=engineers,dc=linuxcent,dc=com

objectClass: inetOrgPerson
objectClass: posixAccount
cn: vamshi
uid: vamshi
uidNumber: 1001
gidNumber: 1001
homeDirectory: /home/vamshi
loginShell: /bin/bash
mail: vamshi@linuxcent.com
</code></pre>
<p>The DN reads right-to-left: domain <code class="" data-line="">linuxcent.com</code> (<code class="" data-line="">dc=linuxcent,dc=com</code>) → organizational unit <code class="" data-line="">engineers</code> → common name <code class="" data-line="">vamshi</code>. Every entry in the directory has a unique path through the tree — there&#8217;s no ambiguity about which <code class="" data-line="">vamshi</code> you mean.</p>
<p>LDAP defines eight operations: Bind (authenticate), Search, Add, Modify, Delete, ModifyDN (rename), Compare, and Abandon. Most of what a Linux authentication system does with LDAP reduces to two: <strong>Bind</strong> (prove you are who you say you are) and <strong>Search</strong> (tell me everything you know about this user).</p>
<p>When your Linux machine authenticates an SSH login against LDAP:</p>
<pre><code class="" data-line="">1. User types password
2. PAM calls pam_sss (or pam_ldap on older systems)
3. SSSD issues a Bind to the LDAP server: &quot;I am cn=vamshi, and here is my credential&quot;
4. LDAP server verifies the bind → success or failure
5. SSSD issues a Search: &quot;give me the posixAccount attributes for uid=vamshi&quot;
6. LDAP returns uidNumber, gidNumber, homeDirectory, loginShell
7. PAM creates the session with those attributes
</code></pre>
<p>The entire login flow is two LDAP operations: one Bind, one Search.</p>
<hr />
<h2 id="try-it-right-now">Try It Right Now</h2>
<p>You don&#8217;t need to set up an LDAP server to run your first query. There&#8217;s a public test LDAP directory at <code class="" data-line="">ldap.forumsys.com</code>:</p>
<pre><code class="" data-line=""># Query a public LDAP server — no setup required
ldapsearch -x \
  -H ldap://ldap.forumsys.com \
  -b &quot;dc=example,dc=com&quot; \
  -D &quot;cn=read-only-admin,dc=example,dc=com&quot; \
  -w readonly \
  &quot;(objectClass=inetOrgPerson)&quot; \
  cn mail uid

# What you get back (abbreviated):
# dn: uid=tesla,dc=example,dc=com
# cn: Tesla
# mail: tesla@ldap.forumsys.com
# uid: tesla
#
# dn: uid=einstein,dc=example,dc=com
# cn: Albert Einstein
# mail: einstein@ldap.forumsys.com
# uid: einstein
</code></pre>
<p>Decode what you just ran:</p>
<ul>
<li><code class="" data-line="">-x</code> — simple authentication (username/password bind, not Kerberos/SASL)</li>
<li><code class="" data-line="">-H ldap://ldap.forumsys.com</code> — the LDAP server URI, port 389</li>
<li><code class="" data-line="">-b &quot;dc=example,dc=com&quot;</code> — the base DN, the top of the subtree to search</li>
<li><code class="" data-line="">-D &quot;cn=read-only-admin,dc=example,dc=com&quot;</code> — the bind DN (who you&#8217;re authenticating as)</li>
<li><code class="" data-line="">-w readonly</code> — the bind password</li>
<li><code class="" data-line="">&quot;(objectClass=inetOrgPerson)&quot;</code> — the search filter: return entries that are people</li>
<li><code class="" data-line="">cn mail uid</code> — the attributes to return (default returns all)</li>
</ul>
<p>That&#8217;s a live LDAP query returning real directory entries from a server running RFC 2251 — the same protocol Tim Howes designed in 1993.</p>
<p>On your own Linux system, if you&#8217;re joined to AD or LDAP, you can query it the same way with your domain credentials.</p>
<hr />
<h2 id="why-it-never-went-away">Why It Never Went Away</h2>
<p>LDAP v3 was finalized in 1997. In 2024, it&#8217;s still the protocol every enterprise directory speaks. Why?</p>
<p>Because it became the lingua franca of enterprise identity before any replacement existed. Every application that needs to authenticate users — VPN concentrators, mail servers, network switches, web applications, HR systems — implemented LDAP support. Every directory service Microsoft, Red Hat, Sun, and Novell shipped stored data in an LDAP-accessible tree.</p>
<p>When Microsoft built Active Directory in 1999, they built it on top of LDAP + Kerberos. When your Linux machine joins an AD domain, it speaks LDAP to enumerate users and groups, and Kerberos to verify credentials. When Okta or Entra ID syncs with your on-premises directory, it uses LDAP Sync (or a modern protocol that maps directly to LDAP semantics).</p>
<p>The protocol is old. The ecosystem built on top of it is so deep that replacing LDAP would mean simultaneously replacing every enterprise application that depends on it. Nobody has done that. Nobody has had to.</p>
<p>What happened instead is the stack got taller. LDAP at the bottom, Kerberos for network authentication, SSSD as the local caching daemon, PAM as the Linux integration layer, SAML and OIDC at the top for web-based federation. The directory is still LDAP. The interfaces above it evolved.</p>
<p>That full stack — from the directory at the bottom to Zero Trust at the top — is what this series covers.</p>
<hr />
<h2 id="common-misconceptions"><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;" /> Common Misconceptions</h2>
<p><strong>&#8220;LDAP is an authentication protocol.&#8221;</strong> LDAP is a directory protocol. It stores identity information and can verify credentials (via Bind). Authentication in modern stacks is typically Kerberos or OIDC — LDAP provides the directory backing it.</p>
<p><strong>&#8220;LDAP is obsolete.&#8221;</strong> LDAP is the storage layer for Active Directory, OpenLDAP, 389-DS, FreeIPA, and every enterprise IdP&#8217;s on-premises sync. It is ubiquitous. What&#8217;s changed is the interface layer above it.</p>
<p><strong>&#8220;You need Active Directory to run LDAP.&#8221;</strong> Active Directory uses LDAP. OpenLDAP, 389-DS, FreeIPA, and Apache Directory Server are all standalone LDAP implementations. You can run a directory without Microsoft.</p>
<p><strong>&#8220;LDAP and LDAPS are different protocols.&#8221;</strong> LDAP is the protocol. LDAPS is LDAP over TLS on port 636. StartTLS is LDAP on port 389 with an in-session upgrade to TLS. Same protocol, different transport security.</p>
<hr />
<h2 id="framework-alignment">Framework Alignment</h2>
<table>
<thead>
<tr>
<th>Domain</th>
<th>Relevance</th>
</tr>
</thead>
<tbody>
<tr>
<td>CISSP Domain 5: Identity and Access Management</td>
<td>LDAP is the foundational directory protocol for centralized identity stores — the base layer of every enterprise IAM stack</td>
</tr>
<tr>
<td>CISSP Domain 4: Communications and Network Security</td>
<td>Port 389 (LDAP), 636 (LDAPS), 3268/3269 (AD Global Catalog) — transport security decisions affect every directory deployment</td>
</tr>
<tr>
<td>CISSP Domain 3: Security Architecture and Engineering</td>
<td>DIT hierarchy, schema design, replication topology — directory structure is an architectural security decision</td>
</tr>
<tr>
<td>NIST SP 800-63B</td>
<td>LDAP as a credential service provider (CSP) backing enterprise authenticators</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="key-takeaways">Key Takeaways</h2>
<ul>
<li>LDAP was invented to solve a real, painful problem: the authentication chaos that NIS couldn&#8217;t fix and X.500/DAP was too expensive to deploy</li>
<li>It inherited the right thing from X.500 (the hierarchical data model) and replaced the right thing (the impractical OSI transport with TCP/IP)</li>
<li>NIS was the predecessor that worked until it didn&#8217;t — its failure modes (no encryption, flat namespace, broadcast discovery) are exactly what LDAP was designed to fix</li>
<li>LDAP v3 (RFC 2251, 1997) is still the production standard — 27 years later</li>
<li>Active Directory, OpenLDAP, FreeIPA, Okta, Entra ID — every enterprise identity system either runs LDAP or speaks it</li>
<li>The full authentication stack is deeper than LDAP: the next 12 episodes peel it apart layer by layer</li>
</ul>
<hr />
<h2 id="whats-next">What&#8217;s Next</h2>
<p>EP01 stayed at the design level — the problem, the predecessor failures, the invention, the data model.</p>
<p>EP02 goes inside the wire. The DIT structure, DN syntax, object classes, schema, and the BER-encoded bytes that actually travel from the server to your authentication daemon. Run <code class="" data-line="">ldapsearch</code> against your own directory and read every line of what comes back.</p>
<p><em>Next: <a href="/ldap-internals-directory-structure/">LDAP Internals: The Directory Tree, Schema, and What Travels on the Wire</a></em></p>
<p>Get EP02 in your inbox when it publishes → <a href="https://linuxcent.com/subscribe">linuxcent.com/subscribe</a></p>
<p><a class="a2a_button_mastodon" href="https://www.addtoany.com/add_to/mastodon?linkurl=https%3A%2F%2Flinuxcent.com%2Fwhat-is-ldap%2F&amp;linkname=What%20Is%20LDAP%20%E2%80%94%20and%20Why%20It%20Was%20Invented%20to%20Replace%20Something%20Worse" title="Mastodon" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_email" href="https://www.addtoany.com/add_to/email?linkurl=https%3A%2F%2Flinuxcent.com%2Fwhat-is-ldap%2F&amp;linkname=What%20Is%20LDAP%20%E2%80%94%20and%20Why%20It%20Was%20Invented%20to%20Replace%20Something%20Worse" title="Email" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_whatsapp" href="https://www.addtoany.com/add_to/whatsapp?linkurl=https%3A%2F%2Flinuxcent.com%2Fwhat-is-ldap%2F&amp;linkname=What%20Is%20LDAP%20%E2%80%94%20and%20Why%20It%20Was%20Invented%20to%20Replace%20Something%20Worse" title="WhatsApp" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_reddit" href="https://www.addtoany.com/add_to/reddit?linkurl=https%3A%2F%2Flinuxcent.com%2Fwhat-is-ldap%2F&amp;linkname=What%20Is%20LDAP%20%E2%80%94%20and%20Why%20It%20Was%20Invented%20to%20Replace%20Something%20Worse" title="Reddit" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_x" href="https://www.addtoany.com/add_to/x?linkurl=https%3A%2F%2Flinuxcent.com%2Fwhat-is-ldap%2F&amp;linkname=What%20Is%20LDAP%20%E2%80%94%20and%20Why%20It%20Was%20Invented%20to%20Replace%20Something%20Worse" title="X" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_linkedin" href="https://www.addtoany.com/add_to/linkedin?linkurl=https%3A%2F%2Flinuxcent.com%2Fwhat-is-ldap%2F&amp;linkname=What%20Is%20LDAP%20%E2%80%94%20and%20Why%20It%20Was%20Invented%20to%20Replace%20Something%20Worse" title="LinkedIn" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_copy_link" href="https://www.addtoany.com/add_to/copy_link?linkurl=https%3A%2F%2Flinuxcent.com%2Fwhat-is-ldap%2F&amp;linkname=What%20Is%20LDAP%20%E2%80%94%20and%20Why%20It%20Was%20Invented%20to%20Replace%20Something%20Worse" title="Copy Link" rel="nofollow noopener" target="_blank"></a><a class="a2a_dd addtoany_share_save addtoany_share" href="https://www.addtoany.com/share#url=https%3A%2F%2Flinuxcent.com%2Fwhat-is-ldap%2F&#038;title=What%20Is%20LDAP%20%E2%80%94%20and%20Why%20It%20Was%20Invented%20to%20Replace%20Something%20Worse" data-a2a-url="https://linuxcent.com/what-is-ldap/" data-a2a-title="What Is LDAP — and Why It Was Invented to Replace Something Worse"></a></p><p>The post <a href="https://linuxcent.com/what-is-ldap/">What Is LDAP — and Why It Was Invented to Replace Something Worse</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://linuxcent.com/what-is-ldap/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1545</post-id>	</item>
		<item>
		<title>Authentication vs Authorization: AWS AccessDenied Explained</title>
		<link>https://linuxcent.com/authentication-vs-authorization-iam/</link>
					<comments>https://linuxcent.com/authentication-vs-authorization-iam/#respond</comments>
		
		<dc:creator><![CDATA[Vamshi Krishna Santhapuri]]></dc:creator>
		<pubDate>Tue, 14 Apr 2026 05:05:02 +0000</pubDate>
				<category><![CDATA[Cloud IAM]]></category>
		<category><![CDATA[Authentication]]></category>
		<category><![CDATA[Authorization]]></category>
		<category><![CDATA[Cloud Security]]></category>
		<category><![CDATA[IAM]]></category>
		<category><![CDATA[Identity Management]]></category>
		<category><![CDATA[MFA]]></category>
		<category><![CDATA[OAuth2]]></category>
		<category><![CDATA[OIDC]]></category>
		<guid isPermaLink="false">https://linuxcent.com/authentication-vs-authorization-iam/</guid>

					<description><![CDATA[<p><span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 10</span> <span class="rt-label rt-postfix">minutes</span></span>Authentication proves identity. Authorization proves permission. Learn why confusing the two creates security gaps — and how cloud IAM implements both correctly.</p>
<p>The post <a href="https://linuxcent.com/authentication-vs-authorization-iam/">Authentication vs Authorization: AWS AccessDenied 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"> 10</span> <span class="rt-label rt-postfix">minutes</span></span><style>
pre{position:relative;background:#1e1e1e;color:#d4d4d4;
    padding:16px 16px 16px 20px;border-radius:6px;overflow-x:auto;
    font-family:'JetBrains Mono','Fira Code','Cascadia Code',Consolas,'Courier New',monospace;
    font-size:.88em;line-height:1.6;border-left:4px solid #555}
code{background:#f4f4f4;padding:2px 5px;border-radius:3px;font-size:.9em}
pre code{background:transparent;padding:0;color:inherit}
pre[data-lang="bash"],pre[data-lang="sh"],
pre[data-lang="shell"],pre[data-lang="zsh"]{border-left-color:#4ec9b0}
pre[data-lang="yaml"],pre[data-lang="json"],
pre[data-lang="toml"],pre[data-lang="xml"]{border-left-color:#569cd6}
pre[data-lang="python"],pre[data-lang="go"],pre[data-lang="rust"],
pre[data-lang="java"],pre[data-lang="c"],pre[data-lang="cpp"]{border-left-color:#c586c0}
pre[data-lang="text"],pre[data-lang="output"],
pre[data-lang="console"]{border-left-color:#888}
.lc-copy-btn{position:absolute;top:8px;right:8px;background:#2d2d2d;color:#ccc;
    border:1px solid #444;border-radius:4px;padding:3px 9px;font-size:.75em;
    font-family:system-ui,sans-serif;cursor:pointer;opacity:0;
    transition:opacity .15s,background .15s;line-height:1.6}
pre:hover .lc-copy-btn{opacity:1}
.lc-copy-btn:hover{background:#3a3a3a;color:#fff}
.lc-copy-btn.copied{color:#4ec9b0;border-color:#4ec9b0}
.lc-lang-badge{position:absolute;top:8px;left:20px;font-family:system-ui,sans-serif;
    font-size:.7em;color:#666;text-transform:uppercase;letter-spacing:.04em;
    line-height:1;pointer-events:none;opacity:0;transition:opacity .15s}
pre:hover .lc-lang-badge{opacity:1}
table{border-collapse:collapse;width:100%;margin:16px 0}
th,td{border:1px solid #ddd;padding:10px 14px;text-align:left}
th{background:#f0f0f0;font-weight:600}
tr:nth-child(even){background:#fafafa}
</style>
<p><script>
(function(){
  if(window.__lcCodeEnhanced)return;
  window.__lcCodeEnhanced=true;
  function enhance(){
    document.querySelectorAll('pre').forEach(function(pre){
      var code=pre.querySelector('code');
      var lang='';
      if(code){var m=(code.className||'').match(/language-(\S+)/);if(m)lang=m[1].toLowerCase();}
      if(lang)pre.setAttribute('data-lang',lang);
      if(lang){var badge=document.createElement('span');badge.className='lc-lang-badge';badge.textContent=lang;pre.insertBefore(badge,pre.firstChild);}
      var btn=document.createElement('button');
      btn.className='lc-copy-btn';btn.textContent='Copy';btn.setAttribute('aria-label','Copy code to clipboard');
      pre.appendChild(btn);
      btn.addEventListener('click',function(){
        var text=code?code.innerText:pre.innerText;
        if(navigator.clipboard&&window.isSecureContext){
          navigator.clipboard.writeText(text).then(function(){ok(btn);}).catch(function(){fb(text,btn);});
        }else{fb(text,btn);}
      });
    });
  }
  function ok(btn){btn.textContent='Copied!';btn.classList.add('copied');setTimeout(function(){btn.textContent='Copy';btn.classList.remove('copied');},2000);}
  function fb(text,btn){
    try{var ta=document.createElement('textarea');ta.value=text;ta.style.cssText='position:fixed;left:-9999px;top:-9999px;opacity:0';document.body.appendChild(ta);ta.select();document.execCommand('copy');document.body.removeChild(ta);ok(btn);}
    catch(e){btn.textContent='✗ Failed';setTimeout(function(){btn.textContent='Copy';},2000);}
  }
  if(document.readyState==='loading'){document.addEventListener('DOMContentLoaded',enhance);}else{enhance();}
})();
</script></p>
<hr />
<p><a href="/what-is-cloud-iam/">What Is Cloud IAM</a> → <strong>Authentication vs Authorization</strong> → <a href="/iam-roles-policies-permissions-explained/">IAM Roles vs Policies</a> → <a href="/aws-iam-deep-dive/">AWS IAM Deep Dive</a> → <a href="/gcp-iam-deep-dive/">GCP Resource Hierarchy IAM</a> → <a href="/azure-rbac-entra-id-guide/">Azure RBAC Scopes</a></p>
<hr />
<h2 id="tldr">TL;DR</h2>
<ul>
<li>Authentication asks <em>are you who you claim to be?</em> Authorization asks <em>are you allowed to do this?</em> — two separate gates, two separate failure modes</li>
<li>AWS <code class="" data-line="">AccessDenied</code> is an <strong>authorization</strong> failure — the identity authenticated fine; fix the policy, not the credentials</li>
<li>Prefer short-lived credentials (STS temporary tokens, Managed Identities) over long-lived access keys — the difference is the blast radius window</li>
<li>MFA strengthens authentication; it does <strong>nothing</strong> for authorization — a hijacked session with broad permissions is just as dangerous with or without MFA on the original login</li>
<li><code class="" data-line="">HTTP 401</code> = authentication failure; <code class="" data-line="">HTTP 403</code> = authorization failure — the code tells you which gate to debug</li>
<li>Both layers must enforce least privilege independently — application-layer authorization is not a substitute for tight cloud IAM</li>
</ul>
<hr />
<h2 id="the-big-picture">The Big Picture</h2>
<p>Every API call in the cloud passes through two gates before it executes. Most engineers know the first one. The second is where most security failures live.</p>
<pre><code class="" data-line="">  THE TWO GATES — every cloud API call passes through both, in order

  ┌──────────────────────────────────────────────────────────────────┐
  │  GATE 1 — AUTHENTICATION                                         │
  │  &quot;Are you who you claim to be?&quot;                                  │
  │                                                                  │
  │  IAM user     →  Access Key + Secret (long-lived, rotatable)    │
  │  IAM role     →  Temporary STS token (expires automatically)    │
  │  Human        →  Password + MFA via console or IdP              │
  │  Service      →  Instance profile / Managed Identity / OIDC     │
  │                                                                  │
  │  Passes → move to Gate 2                                        │
  │  Fails  → stopped here, HTTP 401                                │
  └──────────────────────────────────────────────────────────────────┘
                                 │
                                 ▼
  ┌──────────────────────────────────────────────────────────────────┐
  │  GATE 2 — AUTHORIZATION                                          │
  │  &quot;Are you allowed to do what you&#039;re trying to do?&quot;               │
  │                                                                  │
  │  Evaluated against: identity-based policies · SCPs              │
  │                     resource-based policies · conditions         │
  │                     permissions boundaries · session policies    │
  │                                                                  │
  │  Default answer: DENY (explicit Allow required every time)      │
  │                                                                  │
  │  Passes → request executes                                      │
  │  Fails  → AccessDenied / HTTP 403                               │
  └──────────────────────────────────────────────────────────────────┘

  MFA hardens Gate 1. It has zero effect on Gate 2.
  A hijacked session with a valid token clears Gate 1 automatically.
  Gate 2 is your last line of defense — and the one that&#039;s most often misconfigured.
</code></pre>
<hr />
<h2 id="introduction">Introduction</h2>
<p>The authentication vs authorization distinction is the most commonly confused boundary in cloud security — and the source of most misdirected debugging when an AWS AccessDenied error appears. These are two separate gates, two separate failure modes, and two entirely different fixes.</p>
<p>Early in my career I wrote an API endpoint I was proud of. Token validation. Rejection of unauthenticated requests. I called it &#8220;secured&#8221; in the code review.</p>
<p>A senior engineer asked one question: &#8220;What happens if I take a valid token from a regular user and call your <code class="" data-line="">/admin/delete-user</code> endpoint?&#8221;</p>
<p>I ran the test. It worked. Any employee — with a perfectly valid, properly issued token — could delete any user account in the system.</p>
<p>The authentication was correct. The authorization didn&#8217;t exist.</p>
<p>That gap between <em>proving who you are</em> and <em>proving you&#8217;re allowed to do this</em> is where a surprising number of security incidents live. Not just in application code — in cloud IAM too.</p>
<p>I&#8217;ve reviewed AWS environments where MFA was enforced on every human account, access keys were rotated quarterly, and yet a Lambda function had <code class="" data-line="">s3:*</code> on <code class="" data-line="">*</code> because whoever wrote the deployment script reached for <code class="" data-line="">AmazonS3FullAccess</code> and moved on.</p>
<p>Gate 1 was solid. Gate 2 was wide open.</p>
<p>This episode draws the boundary cleanly — what each gate is, how each cloud implements it, and the specific failure modes that happen when the two get conflated.</p>
<hr />
<h2 id="how-authentication-works-in-cloud-iam">How Authentication Works in Cloud IAM</h2>
<p>Authentication answers: <strong>are you who you claim to be?</strong></p>
<h3 id="the-three-factor-types">The three factor types</h3>
<p>Authentication has not fundamentally changed in decades. What has changed is how cloud platforms implement it.</p>
<table>
<thead>
<tr>
<th>Factor</th>
<th>Type</th>
<th>Cloud Examples</th>
</tr>
</thead>
<tbody>
<tr>
<td>Something you know</td>
<td>Knowledge</td>
<td>Password, access key secret, PIN</td>
</tr>
<tr>
<td>Something you have</td>
<td>Possession</td>
<td>TOTP app, FIDO2 hardware key, smart card</td>
</tr>
<tr>
<td>Something you are</td>
<td>Inherence</td>
<td>Biometrics — less common in cloud contexts</td>
</tr>
</tbody>
</table>
<p>MFA requires two <strong>distinct</strong> factors. A password plus a username is not MFA — both are knowledge factors. A password plus a TOTP code is MFA. Worth stating clearly because I&#8217;ve seen internal documentation describe &#8220;username and password&#8221; as two-factor authentication.</p>
<p>SMS codes count as MFA, but they&#8217;re the weakest form. SIM-swapping attacks — convincing a carrier to port your number — have been used to defeat SMS MFA on high-value accounts. If TOTP or FIDO2 hardware keys are available, use them.</p>
<h3 id="how-aws-authenticates">How AWS authenticates</h3>
<p>AWS has two fundamentally different identity classes:</p>
<p><strong>Human identities</strong> authenticate via console (password + optional MFA) or CLI/API (Access Key ID + Secret Access Key). The access key is a long-lived credential with no default expiry. Every <code class="" data-line="">.env</code> file with an access key, every git commit that included one, every CI/CD log that printed one — that credential is live until someone explicitly rotates or deletes it.</p>
<p><strong>Machine identities</strong> — EC2, Lambda, ECS tasks — authenticate via temporary credentials issued by STS:</p>
<pre><code class="" data-line=""># Assume a role — get temporary credentials that expire
aws sts assume-role \
  --role-arn arn:aws:iam::123456789012:role/DevRole \
  --role-session-name alice-session \
  --duration-seconds 3600
# Returns: AccessKeyId + SecretAccessKey + SessionToken
# All three expire together. Nothing to rotate.

# From inside an EC2 instance — credentials arrive automatically via IMDS
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/MyAppRole
# Returns: AccessKeyId, SecretAccessKey, Token, Expiration
# AWS refreshes these before expiry. The application never sees a rotation event.
</code></pre>
<p>The IMDS model is the right one. The application never manages a credential — it appears, it&#8217;s used, it expires. If it leaks, it&#8217;s usable for hours at most, not years.</p>
<h3 id="why-long-lived-credentials-keep-appearing">Why Long-Lived Credentials Keep Appearing</h3>
<h3 id="how-gcp-authenticates">How GCP authenticates</h3>
<p>GCP cleanly separates human and machine authentication.</p>
<p>Humans authenticate via Google Account or Workspace (OAuth2). The <code class="" data-line="">gcloud</code> CLI handles the flow:</p>
<pre><code class="" data-line="">gcloud auth login                        # browser-based OAuth2 for humans
gcloud auth application-default login    # sets up Application Default Credentials for local dev
</code></pre>
<p>Machine identities use service accounts, ideally attached to the resource rather than using downloaded key files. Key files are GCP&#8217;s equivalent of long-lived AWS access keys — same problems, same risks.</p>
<pre><code class="" data-line=""># From inside a GCE VM — ADC uses the attached service account, no key file needed
gcloud auth print-access-token
# Use it: curl -H &quot;Authorization: Bearer $(gcloud auth print-access-token)&quot; ...
</code></pre>
<h3 id="how-azure-authenticates">How Azure authenticates</h3>
<p>Azure&#8217;s identity plane is Entra ID (formerly Azure Active Directory). Humans authenticate via Entra ID using OAuth2/OIDC. Machine identities use <strong>Managed Identities</strong> — Azure handles the entire credential lifecycle, nothing to configure or rotate.</p>
<pre><code class="" data-line="">az login                                  # browser-based OAuth2
az login --service-principal \            # service principal for automation
  -u APP_ID -p CERT_OR_SECRET \
  --tenant TENANT_ID

# From inside an Azure VM — get a token via IMDS, no credentials needed
curl &#039;http://169.254.169.254/metadata/identity/oauth2/token\
?api-version=2018-02-01&amp;resource=https://management.azure.com/&#039; \
  -H &#039;Metadata: true&#039;
</code></pre>
<h3 id="the-credential-failure-modes-that-repeat-everywhere">The credential failure modes that repeat everywhere</h3>
<p>In practice, the same patterns appear across all three clouds in every audit:</p>
<p><strong>Leaked credentials</strong> — access keys in git commits, <code class="" data-line="">.env</code> files, Docker image layers, CI/CD logs. GitHub&#8217;s secret scanning finds thousands of these monthly on public repos alone.</p>
<p><strong>Long-lived credentials</strong> — an access key from 2019 is still valid in 2026 unless someone explicitly rotated it. I&#8217;ve audited accounts where 30% of access keys had never been rotated, some five years old.</p>
<p><strong>Shared credentials</strong> — one key used by three services. When you revoke it, three things break. When it leaks, you can&#8217;t tell which service was the source.</p>
<p><strong>Credential sprawl</strong> — service account keys downloaded for &#8220;one quick test&#8221; and never deleted. I once found seventeen key files for a single GCP service account, created by different engineers over two years. None rotated. Five belonged to accounts that no longer existed.</p>
<p>The direction of travel in all three clouds is credential-less: workload identity federation, managed identities, instance profiles. We&#8217;ll cover this specifically in <a href="/workload-identity-oidc-service-accounts/">OIDC Workload Identity: Eliminate Cloud Access Keys Entirely</a>.</p>
<hr />
<h2 id="how-authorization-evaluates-every-api-call">How Authorization Evaluates Every API Call</h2>
<p>Authorization happens after authentication. The system knows <em>who</em> you are — now it decides <em>what</em> you can do. This decision is enforced through <a href="/iam-roles-policies-permissions-explained/">IAM roles vs policies</a> — the building blocks that express what each identity is allowed to do on which resources.</p>
<h3 id="what-the-evaluation-looks-like">What the evaluation looks like</h3>
<p>Every API call triggers an authorization check. You don&#8217;t notice when it succeeds. You notice when it fails:</p>
<pre><code class="" data-line="">REQUEST:
  Action:    s3:DeleteObject
  Resource:  arn:aws:s3:::prod-backups/2024-01-15.tar.gz
  Principal: arn:aws:iam::123456789012:role/DevEngineerRole
  Context:   { source_ip: &quot;10.0.1.5&quot;, mfa: false, time: &quot;14:32 UTC&quot; }

EVALUATION:
  1. Explicit Deny anywhere? → none found
  2. Explicit Allow in any policy? → not granted
  3. Default → DENY

RESULT: AccessDenied
</code></pre>
<p>The engineer authenticated successfully. Valid credentials, valid session. But <code class="" data-line="">DevEngineerRole</code> has no policy granting <code class="" data-line="">s3:DeleteObject</code> on that bucket. Gate 1 passed. Gate 2 denied. They are evaluated independently.</p>
<h3 id="policy-evaluation-chains-by-cloud">Policy evaluation chains by cloud</h3>
<p><strong>AWS — evaluated in layers, explicit Deny wins at any layer:</strong></p>
<pre><code class="" data-line="">1. Explicit Deny in any SCP?           → DENY (cannot be overridden anywhere)
2. No SCP Allow?                       → DENY
3. Explicit Deny in identity or resource policy? → DENY
4. Resource-based policy Allow?        → can ALLOW (same account)
5. Permissions boundary — no Allow?    → DENY
6. Session policy — no Allow?          → DENY
7. Identity-based policy Allow?        → ALLOW
Default (nothing granted):             → DENY
</code></pre>
<p>The default is always Deny. Every successful authorization is an explicit <code class="" data-line="">&quot;Effect&quot;: &quot;Allow&quot;</code> somewhere in the chain. This is the opposite of traditional Unix — in the cloud, if you didn&#8217;t explicitly grant it, it doesn&#8217;t exist.</p>
<p><strong>GCP — additive, permissions accumulate up the hierarchy:</strong></p>
<pre><code class="" data-line="">Permission granted if ANY binding grants it at:
  resource level → project level → folder level → organization level

IAM Deny Policies can override all grants (newer feature).
No binding at any level? → Denied.
</code></pre>
<p><strong>Azure RBAC:</strong></p>
<pre><code class="" data-line="">1. Explicit Deny Assignment?           → DENY (even Owner can&#039;t override)
2. Role Assignment with Allow?         → ALLOW
Default:                               → DENY
</code></pre>
<hr />
<h2 id="why-confusing-authentication-and-authorization-breaks-security">Why Confusing Authentication and Authorization Breaks Security</h2>
<h3 id="the-token-as-authorization-antipattern">The token-as-authorization antipattern</h3>
<p>An application checks for a valid JWT and if found, proceeds. The JWT proves the user authenticated with the IdP. However, it says nothing about what they&#8217;re allowed to do.</p>
<pre><code class="" data-line=""># This is authentication only — anyone with a valid token gets through
@app.route(&quot;/admin/delete-user&quot;, methods=[&quot;POST&quot;])
def delete_user():
    token = request.headers.get(&quot;Authorization&quot;)
    if verify_token(token):           # asks: is this token real and unexpired?
        delete_user_from_db(...)      # executes for any valid token holder
        return &quot;OK&quot;
    return &quot;Unauthorized&quot;, 401

# This separates the two correctly
@app.route(&quot;/admin/delete-user&quot;, methods=[&quot;POST&quot;])
def delete_user():
    token = request.headers.get(&quot;Authorization&quot;)
    principal = verify_token(token)                    # Gate 1: authentication
    if not has_permission(principal, &quot;users:delete&quot;):  # Gate 2: authorization
        return &quot;Forbidden&quot;, 403
    delete_user_from_db(...)
    return &quot;OK&quot;
</code></pre>
<h3 id="the-short-expiry-principle">The short-expiry principle</h3>
<table>
<thead>
<tr>
<th>Credential type</th>
<th>Provider</th>
<th>Typical lifetime</th>
<th>Risk</th>
</tr>
</thead>
<tbody>
<tr>
<td>Access Key + Secret</td>
<td>AWS</td>
<td>Permanent (until deleted)</td>
<td>Years of exposure if leaked</td>
</tr>
<tr>
<td>STS Temporary Token</td>
<td>AWS</td>
<td>15 min – 12 hours</td>
<td>Hours at most</td>
</tr>
<tr>
<td>OAuth2 Access Token</td>
<td>GCP / Azure</td>
<td>~1 hour</td>
<td>Short window</td>
</tr>
<tr>
<td>IMDS Token (VM)</td>
<td>All three</td>
<td>Minutes</td>
<td>Auto-refreshed by platform</td>
</tr>
</tbody>
</table>
<p>A credential that expires in an hour has a one-hour exposure window if stolen. A credential that never expires has an unlimited window. This is the operational argument for managed identities and instance profiles, beyond just convenience.</p>
<pre><code class="" data-line=""># AWS — configure max session duration at role level
aws iam update-role \
  --role-name MyRole \
  --max-session-duration 3600   # 1 hour max

# GCP — access tokens expire in ~1 hour automatically
gcloud auth print-access-token
# Refresh: gcloud auth application-default print-access-token

# Azure — token lifetime configurable in Entra ID token policies
az account get-access-token --resource https://management.azure.com/
</code></pre>
<hr />
<h2 id="production-gotchas"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Production Gotchas</h2>
<pre><code class="" data-line="">╔══════════════════════════════════════════════════════════════════════╗
║  &#x26a0;  GOTCHA 1 — &quot;We have MFA, so permissions can be broad&quot;          ║
║                                                                      ║
║  MFA protects Gate 1 only. If a session is hijacked after login    ║
║  (via malware, SSRF, or a stolen session cookie), the attacker has  ║
║  a valid, MFA-authenticated token. Gate 1 is already cleared.       ║
║  Broad permissions in Gate 2 are the full attack surface.           ║
║                                                                      ║
║  Fix: treat Gate 2 (IAM policy) as your primary blast-radius        ║
║  control. MFA buys time. Least privilege limits damage.             ║
╚══════════════════════════════════════════════════════════════════════╝

╔══════════════════════════════════════════════════════════════════════╗
║  &#x26a0;  GOTCHA 2 — Debugging AccessDenied by rotating credentials      ║
║                                                                      ║
║  AWS AccessDenied is an authorization failure. The identity         ║
║  authenticated successfully — there&#039;s no Allow in the policy.       ║
║  Rotating the access key does nothing.                              ║
║                                                                      ║
║  Fix: check the policy chain. Use simulate-principal-policy to      ║
║  confirm where the Allow is missing before touching credentials.    ║
╚══════════════════════════════════════════════════════════════════════╝

╔══════════════════════════════════════════════════════════════════════╗
║  &#x26a0;  GOTCHA 3 — Application-layer authZ with broad cloud IAM        ║
║                                                                      ║
║  &quot;The app controls access&quot; is not a substitute for scoped cloud     ║
║  IAM. An SSRF vulnerability, exposed debug endpoint, or            ║
║  compromised dependency bypasses the application layer entirely.    ║
║  The cloud identity&#039;s permissions become the attacker&#039;s surface.    ║
║                                                                      ║
║  Fix: both layers enforce least privilege independently.            ║
╚══════════════════════════════════════════════════════════════════════╝
</code></pre>
<hr />
<h2 id="authentication-vs-authorization-audit-checklist">Authentication vs Authorization Audit Checklist</h2>
<p>Split your IAM review along the authN/authZ boundary — they&#8217;re different problems with different fixes.</p>
<p><strong>Authentication — Gate 1:</strong><br />
&#8211; Are there long-lived access keys that could be replaced with STS/Managed Identity?<br />
&#8211; Is MFA enforced for all human identities with console or API access?<br />
&#8211; Are service account key files present where workload identity is available?<br />
&#8211; Are credentials stored in a secrets manager — not in code, <code class="" data-line="">.env</code> files, or repos?<br />
&#8211; When did each long-lived credential last rotate?</p>
<p><strong>Authorization — Gate 2:</strong><br />
&#8211; Does every policy follow least privilege — only the permissions the workload actually uses?<br />
&#8211; Are there wildcards (<code class="" data-line="">s3:*</code>, <code class="" data-line="">&quot;Resource&quot;: &quot;*&quot;</code>) that could be narrowed?<br />
&#8211; Are write, delete, and IAM-modification actions scoped to specific resources?<br />
&#8211; Are SCPs or permissions boundaries capping maximum permissions at org or account level?<br />
&#8211; When were each role&#8217;s permissions last reviewed against actual usage (Access Analyzer)?</p>
<hr />
<h2 id="quick-reference">Quick Reference</h2>
<pre><code class="" data-line="">┌────────────────────────────┬──────────────────────────────────────────────────┐
│ Term                       │ What it means                                    │
├────────────────────────────┼──────────────────────────────────────────────────┤
│ Authentication (AuthN)     │ Verifying identity — are you who you claim?      │
│ Authorization (AuthZ)      │ Verifying permission — are you allowed to act?   │
│ MFA                        │ Two distinct factors; strengthens Gate 1 only    │
│ STS (AWS)                  │ Security Token Service — issues temp credentials │
│ Access Key                 │ Long-lived AWS credential; avoid for services    │
│ Instance profile (AWS)     │ Container attaching a role to EC2                │
│ Managed Identity (Azure)   │ Credential-less identity for Azure services      │
│ Service Account (GCP)      │ Machine identity; prefer attached over key file  │
│ HTTP 401                   │ Authentication failure — prove who you are       │
│ HTTP 403 / AccessDenied    │ Authorization failure — fix the policy           │
└────────────────────────────┴──────────────────────────────────────────────────┘

Commands to know:
┌──────────────────────────────────────────────────────────────────────────────┐
│  # AWS — assume a role and get temporary credentials                        │
│  aws sts assume-role --role-arn arn:aws:iam::ACCOUNT:role/ROLE \            │
│    --role-session-name my-session --duration-seconds 3600                   │
│                                                                              │
│  # AWS — simulate a policy to debug AccessDenied before touching anything   │
│  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/*                                 │
│                                                                              │
│  # AWS — check what credentials your session is using                       │
│  aws sts get-caller-identity                                                 │
│                                                                              │
│  # GCP — print the current access token (expires in ~1 hour)                │
│  gcloud auth print-access-token                                              │
│                                                                              │
│  # GCP — show which account ADC is using                                    │
│  gcloud auth application-default print-access-token                         │
│                                                                              │
│  # Azure — get current token for ARM                                         │
│  az account get-access-token --resource https://management.azure.com/       │
│                                                                              │
│  # Azure — check who you&#039;re logged in as                                     │
│  az account show                                                             │
└──────────────────────────────────────────────────────────────────────────────┘
</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>AuthN and AuthZ are the two core mechanisms; this episode defines the boundary</td>
</tr>
<tr>
<td>CISSP</td>
<td>Domain 1 — Security &amp; Risk Management</td>
<td>Conflating the two creates systematic, measurable risk with different attack surfaces</td>
</tr>
<tr>
<td>ISO 27001:2022</td>
<td>5.17 Authentication information</td>
<td>Managing credentials and authentication mechanisms across the identity lifecycle</td>
</tr>
<tr>
<td>ISO 27001:2022</td>
<td>8.5 Secure authentication</td>
<td>Technical controls — MFA, session management, credential policies</td>
</tr>
<tr>
<td>ISO 27001:2022</td>
<td>5.15 Access control</td>
<td>Policy requirements that depend on cleanly separating identity from permission</td>
</tr>
<tr>
<td>SOC 2</td>
<td>CC6.1</td>
<td>Logical access controls — this episode defines the two-gate model CC6.1 is built on</td>
</tr>
<tr>
<td>SOC 2</td>
<td>CC6.7</td>
<td>Access restrictions enforced at the authorization layer, not just authentication</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="key-takeaways">Key Takeaways</h2>
<ul>
<li>Authentication proves identity; authorization proves permission — two gates, two separate failure modes, two separate fixes</li>
<li>AWS <code class="" data-line="">AccessDenied</code> is a Gate 2 failure — the credential is valid, the policy is missing; fix the policy</li>
<li>Short-lived credentials (STS, Managed Identities, instance profiles) reduce the blast radius of a credential compromise from years to hours</li>
<li>MFA hardens Gate 1 — it has no effect on what an authenticated identity can do</li>
<li>HTTP 401 = Gate 1 failed; HTTP 403 = Gate 2 failed — the status code tells you where to look</li>
<li>Application-layer authorization and cloud IAM authorization are independent — both must enforce least privilege</li>
</ul>
<hr />
<h2 id="whats-next">What&#8217;s Next</h2>
<p>You now know what the two gates are and where failures in each originate. <a href="/iam-roles-policies-permissions-explained/">IAM Roles vs Policies: How Cloud Authorization Actually Works</a> goes into the mechanics of Gate 2 — the permissions, policies, and roles that implement authorization in practice, and the structural patterns that keep them from turning into an unmanageable sprawl.</p>
<p><em>Next: <a href="/iam-roles-policies-permissions-explained/">IAM Roles vs Policies: How Cloud Authorization Actually Works</a></em></p>
<p>Get the IAM roles vs policies breakdown in your inbox when it publishes → <a href="https://linuxcent.com/subscribe">linuxcent.com/subscribe</a></p>
<p><a class="a2a_button_mastodon" href="https://www.addtoany.com/add_to/mastodon?linkurl=https%3A%2F%2Flinuxcent.com%2Fauthentication-vs-authorization-iam%2F&amp;linkname=Authentication%20vs%20Authorization%3A%20AWS%20AccessDenied%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%2Fauthentication-vs-authorization-iam%2F&amp;linkname=Authentication%20vs%20Authorization%3A%20AWS%20AccessDenied%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%2Fauthentication-vs-authorization-iam%2F&amp;linkname=Authentication%20vs%20Authorization%3A%20AWS%20AccessDenied%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%2Fauthentication-vs-authorization-iam%2F&amp;linkname=Authentication%20vs%20Authorization%3A%20AWS%20AccessDenied%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%2Fauthentication-vs-authorization-iam%2F&amp;linkname=Authentication%20vs%20Authorization%3A%20AWS%20AccessDenied%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%2Fauthentication-vs-authorization-iam%2F&amp;linkname=Authentication%20vs%20Authorization%3A%20AWS%20AccessDenied%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%2Fauthentication-vs-authorization-iam%2F&amp;linkname=Authentication%20vs%20Authorization%3A%20AWS%20AccessDenied%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%2Fauthentication-vs-authorization-iam%2F&#038;title=Authentication%20vs%20Authorization%3A%20AWS%20AccessDenied%20Explained" data-a2a-url="https://linuxcent.com/authentication-vs-authorization-iam/" data-a2a-title="Authentication vs Authorization: AWS AccessDenied Explained"></a></p><p>The post <a href="https://linuxcent.com/authentication-vs-authorization-iam/">Authentication vs Authorization: AWS AccessDenied Explained</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://linuxcent.com/authentication-vs-authorization-iam/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1456</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-08-30 13:25:59 by W3 Total Cache
-->