<?xml version="1.0" encoding="utf-8" standalone="yes"?><?xml-stylesheet href="/feed_style.xsl" type="text/xsl"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="https://www.rssboard.org/media-rss">
  <channel>
    <title>strawberry on MR70</title>
    <link>https://www.mr70.eu/tags/strawberry/</link>
    <description>Recent content in strawberry on MR70</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <copyright>MR70 - [Creative Commons Attribution 4.0 International License](https://creativecommons.org/licenses/by/4.0/).</copyright>
    <lastBuildDate>Sun, 25 Oct 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://www.mr70.eu/tags/strawberry/index.xml" rel="self" type="application/rss+xml" /><icon>https://www.mr70.eu/logo.svg</icon>
    
    
    <item>
      <title>graphqlAD - a read-only GraphQL API for Microsoft Active Directory</title>
      <link>https://www.mr70.eu/posts/graphqlad-graphql-api-active-directory/</link>
      <pubDate>Sun, 25 Oct 2026 00:00:00 +0000</pubDate>
      
      <guid>https://www.mr70.eu/posts/graphqlad-graphql-api-active-directory/</guid>
      <description><![CDATA[<h2 id="graphqlad">graphqlAD</h2>
<p><img src="/img/msad-ecosystem.svg" alt="The msad ecosystem: msad at the core, with mcpAD (MCP), restAD (REST) and graphqlAD (GraphQL) around it"></p>
<p><a href="https://github.com/matteoredaelli/graphqlAD">graphqlAD</a> is a <strong>read-only</strong> GraphQL interface over the <a href="https://github.com/matteoredaelli/msad"><code>msad</code></a> Active Directory / LDAP library, built with <strong>Strawberry</strong> + <strong>FastAPI</strong>, managed with <strong>uv</strong> on <strong>Python 3.14</strong>.</p>
<p>The schema has a single <code>Query</code> type and <strong>no <code>Mutation</code> type</strong>, so no write operation (create / modify / delete / password reset / group membership changes) is reachable through the API — only LDAP <em>search</em> operations are exposed.</p>
<p>It is a GraphQL sibling of <a href="/posts/restad-rest-api-active-directory/">restAD</a> (REST) and <a href="/posts/mcpad-mcp-server-active-directory/">mcpAD</a> (MCP): all three are thin, read-only front ends calling the same typed <code>msad</code> API directly.</p>
<h2 id="typed-objects-and-virtual-fields">Typed objects and virtual fields</h2>
<p>This is where graphqlAD earns its keep over a plain JSON API: queries that return users, groups, computers or OUs expose <strong>typed GraphQL objects</strong> (<code>User</code>, <code>Group</code>, <code>Computer</code>, <code>Ou</code>) instead of bare JSON. Each one carries:</p>
<ul>
<li><code>raw</code> — the complete, dynamic LDAP entry as a <code>JSON</code> scalar (nothing is lost).</li>
<li>a few convenience identity scalars (e.g. <code>samAccountName</code>, <code>displayName</code>, <code>cn</code>).</li>
<li><strong>virtual fields</strong> — DN-valued attributes expanded into full objects, resolved <em>lazily</em> (only when selected) through a per-request DataLoader that deduplicates and batches lookups:
<ul>
<li><code>User.managerUser</code>, <code>User.memberOfGroups</code>, <code>User.directReportsUsers</code></li>
<li><code>Group.managedByUser</code>, <code>Group.memberUsers</code>, <code>Group.memberGroups</code>, <code>Group.memberOfGroups</code></li>
<li><code>Group.members: [GroupMember!]!</code> — every member resolved to its <strong>concrete type</strong> via a <code>User | Group | Computer</code> union, so nested groups and computers are kept (not just flattened to users)</li>
<li><code>Computer.managedByUser</code>, <code>Ou.managedByUser</code></li>
</ul>
</li>
</ul>
<p>That last point means you can write a query like this and walk the directory graph in one round trip:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-graphql" data-lang="graphql"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  group(identifier: <span style="color:#f1fa8c">&#34;qlik_analyzer_users&#34;</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#50fa7b">cn</span>
</span></span><span style="display:flex;"><span>    managedByUser { displayName mail }
</span></span><span style="display:flex;"><span>    members {
</span></span><span style="display:flex;"><span>      __typename
</span></span><span style="display:flex;"><span>      <span style="color:#8be9fd;font-style:italic">...</span> <span style="color:#8be9fd;font-style:italic">on</span> User { samAccountName displayName }
</span></span><span style="display:flex;"><span>      <span style="color:#8be9fd;font-style:italic">...</span> <span style="color:#8be9fd;font-style:italic">on</span> Group { cn }
</span></span><span style="display:flex;"><span>      <span style="color:#8be9fd;font-style:italic">...</span> <span style="color:#8be9fd;font-style:italic">on</span> Computer { dnsHostName operatingSystem }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Queries whose results mix object types (e.g. OU contents) or have no DN worth expanding (e.g. contacts) just return the generic <code>Entries</code>/<code>Entry</code> JSON envelope instead.</p>
<h2 id="available-queries">Available queries</h2>
<ul>
<li><code>health</code></li>
<li><strong>Users</strong> (→ <code>User</code>) — <code>users</code>, <code>inactiveUsers</code>, <code>user</code>, <code>userStatus</code>, <code>neverExpiringAccounts</code>, <code>expiredAccounts</code>, <code>passwordNotRequired</code>, <code>kerberosPreauthNotRequired</code>, <code>notDelegated</code> (risk checks)</li>
<li><strong>Computers</strong> (→ <code>Computer</code>) — <code>computers</code> (optionally <code>excludeDomainControllers: true</code>), <code>staleComputers</code>, <code>domainControllers</code>, <code>osSummary</code>, <code>computer</code></li>
<li><strong>Groups</strong> (→ <code>Group</code>) — <code>groups</code>, <code>emptyGroups</code>, <code>securityGroups</code> (optionally <code>scope: &quot;global&quot; | &quot;domain_local&quot; | &quot;universal&quot;</code>), <code>group</code>, <code>userGroups</code>, <code>groupMembers</code> (→ <code>User</code>), <code>isMember</code></li>
<li><strong>Contacts</strong> — <code>contacts</code></li>
<li><strong>OUs</strong> (→ <code>Ou</code>) — <code>ous</code>, <code>ou</code>; <code>ouContents</code> (mixed object types)</li>
<li><strong>Audit</strong> — <code>domainInfo</code>, <code>passwordPolicy</code>, <code>passwordPolicyViolations</code> (→ <code>User</code>), <code>privilegedGroups</code> (→ <code>Group</code>), <code>fsmoRoles</code>, <code>trusts</code>, <code>sites</code>, <code>gpos</code>, <code>gpo</code>, <code>fineGrainedPasswordPolicies</code></li>
</ul>
<h2 id="authentication-and-configuration">Authentication and configuration</h2>
<p>Same model as restAD: the effective connection config is resolved from an optional <code>~/.msad.toml</code> (or <code>MSAD_CONFIG_FILE</code>), with <code>MSAD_*</code> environment variables (<code>MSAD_HOST</code>, <code>MSAD_BASE</code>, <code>MSAD_PORT</code>, <code>MSAD_USE_SSL</code>, <code>MSAD_USER</code>, <code>MSAD_PASSWORD</code>) always winning. Omitting <code>MSAD_USER</code>/<code>MSAD_PASSWORD</code> falls back to <strong>Kerberos/SASL</strong> (run <code>kinit</code> first); setting both uses a simple <strong>user/password</strong> bind.</p>
<p>Server behavior is controlled by <code>GRAPHQLAD_HOST</code>, <code>GRAPHQLAD_PORT</code>, <code>GRAPHQLAD_LAZY_CONNECT</code> and <code>GRAPHQLAD_GRAPHIQL</code> (toggle the in-browser GraphiQL IDE, on by default).</p>
<h2 id="running">Running</h2>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">export</span> <span style="color:#8be9fd;font-style:italic">MSAD_HOST</span><span style="color:#ff79c6">=</span>dc.example.com
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">export</span> <span style="color:#8be9fd;font-style:italic">MSAD_BASE</span><span style="color:#ff79c6">=</span><span style="color:#f1fa8c">&#39;dc=example,dc=com&#39;</span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">export</span> <span style="color:#8be9fd;font-style:italic">MSAD_PORT</span><span style="color:#ff79c6">=</span><span style="color:#bd93f9">636</span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">export</span> <span style="color:#8be9fd;font-style:italic">MSAD_USE_SSL</span><span style="color:#ff79c6">=</span><span style="color:#8be9fd;font-style:italic">true</span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">export</span> <span style="color:#8be9fd;font-style:italic">MSAD_USER</span><span style="color:#ff79c6">=</span><span style="color:#f1fa8c">&#39;svc_reader&#39;</span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">export</span> <span style="color:#8be9fd;font-style:italic">MSAD_PASSWORD</span><span style="color:#ff79c6">=</span><span style="color:#f1fa8c">&#39;s3cr3t&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>uv run graphqlad
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># or: uv run uvicorn graphqlad.app:app --host 0.0.0.0 --port 8000</span>
</span></span></code></pre></div><p>Then open GraphiQL at <code>http://127.0.0.1:8000/graphql</code> (the same URL serves <code>POST</code> queries).</p>
<h3 id="run-with-uvx">Run with uvx</h3>
<p>No clone or install needed:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">MSAD_HOST</span><span style="color:#ff79c6">=</span>dc.example.com <span style="color:#8be9fd;font-style:italic">MSAD_BASE</span><span style="color:#ff79c6">=</span><span style="color:#f1fa8c">&#39;dc=example,dc=com&#39;</span> <span style="color:#f1fa8c">\
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c"></span><span style="color:#8be9fd;font-style:italic">MSAD_USER</span><span style="color:#ff79c6">=</span>svc_reader <span style="color:#8be9fd;font-style:italic">MSAD_PASSWORD</span><span style="color:#ff79c6">=</span><span style="color:#f1fa8c">&#39;s3cr3t&#39;</span> <span style="color:#f1fa8c">\
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c"></span>  uvx graphqlad
</span></span></code></pre></div><p>Kerberos (SASL/GSSAPI) authentication is optional: <code>msad</code>&rsquo;s <code>gssapi</code> dependency is a native extension that needs the system Kerberos dev headers to build (e.g. <code>libkrb5-dev</code> on Debian/Ubuntu, <code>krb5-devel</code> on RHEL/Fedora, <code>krb5</code> via Homebrew on macOS). If you only authenticate with user/password, skip it entirely; otherwise install the <code>kerberos</code> extra:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>uvx --from <span style="color:#f1fa8c">&#39;graphqlad[kerberos]&#39;</span> graphqlad
</span></span></code></pre></div><h2 id="why-it-is-useful">Why it is useful</h2>
<p>GraphQL&rsquo;s single-request, client-driven field selection is a natural fit for directory data: instead of chaining multiple REST calls to resolve a user&rsquo;s manager or a group&rsquo;s members, a client asks for exactly the shape it needs — including the DN-expanded objects — in one query, and gets back only what it asked for. Combined with the read-only-by-construction schema, it&rsquo;s a safe way to let dashboards, internal tools or AI agents explore Active Directory without any risk of mutating it.</p>
<p>The code is available on <a href="https://github.com/matteoredaelli/graphqlAD">GitHub</a> and the package is published on <a href="https://pypi.org/project/graphqlad/">PyPI</a>.</p>
]]></description>
      
    </item>
    
    
  </channel>
</rss>
