Dotfiles/docs/html/freeipa-ansible.html

442 lines
18 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>FreeIPA & Ansible — M-Archy Dotfiles</title>
<style>
/* ── CyberQueer Theme ──────────────────────────────────────────────── */
:root {
--bg: #1A1A1A;
--bg2: #242424;
--bg3: #2e2e2e;
--text: #D6ABAB;
--accent: #E40046;
--violet: #5018DD;
--danger: #F50505;
--border: #5018DD;
--radius: 10px;
--radius-sm: 5px;
}
/* ── Reset & Base ──────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html { scroll-behavior: smooth; }
body {
background-color: var(--bg);
color: var(--text);
font-family: 'Agave Nerd Font Mono', 'JetBrains Mono', 'Fira Code',
'Cascadia Code', 'Source Code Pro', monospace;
font-size: 15px;
line-height: 1.75;
max-width: 960px;
margin: 0 auto;
padding: 2.5rem 2rem 5rem;
}
/* ── Typography ────────────────────────────────────────────────────── */
h1, h2, h3, h4, h5, h6 {
font-family: 'Agave Nerd Font Mono', monospace;
font-weight: 700;
line-height: 1.25;
margin-top: 2.2rem;
margin-bottom: 0.6rem;
}
h1 {
color: var(--accent);
font-size: 2.2rem;
border-bottom: 3px solid var(--accent);
padding-bottom: 0.4rem;
margin-top: 0;
}
h2 {
color: var(--accent);
font-size: 1.55rem;
border-bottom: 2px solid var(--violet);
padding-bottom: 0.3rem;
}
h3 {
color: var(--violet);
font-size: 1.2rem;
}
h4, h5, h6 {
color: var(--text);
font-size: 1rem;
}
p {
margin: 0.9rem 0;
}
strong { color: var(--accent); font-weight: 700; }
em { color: var(--violet); font-style: italic; }
/* ── Links ─────────────────────────────────────────────────────────── */
a {
color: var(--violet);
text-decoration: none;
border-bottom: 1px solid transparent;
transition: color 0.15s, border-color 0.15s;
}
a:hover {
color: var(--accent);
border-bottom-color: var(--accent);
}
/* ── Code ──────────────────────────────────────────────────────────── */
code {
font-family: inherit;
background: var(--bg2);
color: var(--violet);
border: 1px solid var(--violet);
border-radius: var(--radius-sm);
padding: 0.1em 0.42em;
font-size: 0.9em;
}
pre {
background: var(--bg2);
border: 2px solid var(--violet);
border-radius: var(--radius);
padding: 1.2rem 1.4rem;
overflow-x: auto;
margin: 1.2rem 0;
position: relative;
}
pre code {
background: transparent;
border: none;
padding: 0;
color: var(--text);
font-size: 0.875em;
line-height: 1.6;
}
/* Syntax-like token colouring (no JS required — structural only) */
pre code .kw { color: var(--accent); }
pre code .str { color: var(--violet); }
pre code .cm { color: #666; font-style: italic; }
/* ── Horizontal Rule ───────────────────────────────────────────────── */
hr {
border: none;
border-top: 2px solid var(--violet);
margin: 2rem 0;
opacity: 0.45;
}
/* ── Blockquote ────────────────────────────────────────────────────── */
blockquote {
border-left: 4px solid var(--accent);
background: var(--bg2);
margin: 1.2rem 0;
padding: 0.8rem 1.2rem;
border-radius: 0 var(--radius) var(--radius) 0;
color: var(--text);
opacity: 0.9;
}
blockquote p { margin: 0; }
/* ── Tables ────────────────────────────────────────────────────────── */
table {
width: 100%;
border-collapse: collapse;
border: 2px solid var(--border);
border-radius: var(--radius);
overflow: hidden;
margin: 1.2rem 0;
font-size: 0.9em;
}
th {
background: var(--violet);
color: var(--bg);
text-align: left;
padding: 0.55rem 0.9rem;
font-weight: 700;
letter-spacing: 0.03em;
}
td {
padding: 0.5rem 0.9rem;
border-top: 1px solid var(--bg3);
vertical-align: top;
}
tr:nth-child(even) td { background: var(--bg2); }
tr:hover td { background: var(--bg3); }
/* ── Lists ─────────────────────────────────────────────────────────── */
ul, ol {
padding-left: 1.6rem;
margin: 0.7rem 0;
}
li { margin: 0.25rem 0; }
ul li::marker { color: var(--accent); }
ol li::marker { color: var(--violet); font-weight: 700; }
/* ── Nav sidebar (index page) ──────────────────────────────────────── */
nav ul {
list-style: none;
padding: 0;
}
nav li { margin: 0.3rem 0; }
/* ── Page Header Bar ───────────────────────────────────────────────── */
.page-header {
border-bottom: 3px solid var(--accent);
padding-bottom: 0.6rem;
margin-bottom: 2rem;
display: flex;
align-items: baseline;
gap: 1rem;
}
.page-header .site-title {
color: var(--accent);
font-size: 0.85rem;
opacity: 0.75;
white-space: nowrap;
}
/* ── Back link ─────────────────────────────────────────────────────── */
.back-link {
display: inline-block;
color: var(--violet);
font-size: 0.85rem;
margin-bottom: 1.5rem;
padding: 0.25rem 0.75rem;
border: 1px solid var(--violet);
border-radius: 30px;
transition: background 0.15s, color 0.15s;
}
.back-link:hover {
background: var(--violet);
color: var(--bg);
border-bottom-color: transparent;
}
/* ── Footer ────────────────────────────────────────────────────────── */
footer {
margin-top: 4rem;
padding-top: 1rem;
border-top: 1px solid var(--bg3);
color: var(--text);
opacity: 0.4;
font-size: 0.8rem;
text-align: center;
}
/* ── Responsive ────────────────────────────────────────────────────── */
@media (max-width: 680px) {
body { padding: 1.2rem 1rem 3rem; font-size: 14px; }
h1 { font-size: 1.7rem; }
h2 { font-size: 1.3rem; }
table { display: block; overflow-x: auto; }
}
/* ── Selection ─────────────────────────────────────────────────────── */
::selection {
background: var(--accent);
color: var(--bg);
}
/* ── Scrollbar ─────────────────────────────────────────────────────── */
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: var(--bg2); }
::-webkit-scrollbar-thumb { background: var(--violet); border-radius: 4px; }
::-webkit-scrollbar-thumb:hover { background: var(--accent); }
</style>
</head>
<body>
<header class="page-header">
<span class="site-title">M-Archy Dotfiles</span>
</header>
<a class="back-link" href="index.html">← Index</a>
<main>
<h1 id="freeipa-ansible">FreeIPA &amp; Ansible<a class="toc-anchor" href="#freeipa-ansible" title="Permanent link">&para;</a></h1>
<p>The FreeIPA/Ansible system provides centralised identity management for a fleet of Arch Linux machines: single sign-on, host-group-driven package and module deployment, LUKS backup key collection, and automatic Keycloak configuration.</p>
<p>All relevant files live under <code>setup/modules/FreeipaAnsible/</code>.</p>
<hr />
<h2 id="architecture">Architecture<a class="toc-anchor" href="#architecture" title="Permanent link">&para;</a></h2>
<pre><code>┌────────────────────────────────────┐
│ FreeIPA Server │
│ (can run in Docker / LXC) │
│ │
│ • User/host directory │
│ • Kerberos KDC │
│ • DNS (optional) │
│ • Host group management │
└──────────┬─────────────────────────┘
│ SSSD / Kerberos
┌────────────────────────────────────┐
│ Enrolled client machine │
│ │
│ • sssd — authentication │
│ • ipa CLI — host group queries │
│ • Ansible-deployed timers │
│ ├── package installer │
│ ├── module installer │
│ ├── Flatpak installer │
│ └── baseuser group sync │
└────────────────────────────────────┘
</code></pre>
<hr />
<h2 id="freeipa-server">FreeIPA Server<a class="toc-anchor" href="#freeipa-server" title="Permanent link">&para;</a></h2>
<h3 id="docker-oci-image">Docker / OCI Image<a class="toc-anchor" href="#docker-oci-image" title="Permanent link">&para;</a></h3>
<p>A pre-built Docker image is available via <code>setup/modules/FreeipaAnsible/image/</code>:</p>
<pre><code class="language-bash">cd setup/modules/FreeipaAnsible/image
cp .env.example .env
# Edit .env with your domain, admin password, realm, etc.
docker compose up -d
</code></pre>
<p>The container runs <code>ipa-first-boot.sh</code> on first start to initialise the IPA instance, then optionally <code>keycloak-configure.sh</code> to wire up Keycloak as an OIDC provider.</p>
<h3 id="interactive-server-setup">Interactive Server Setup<a class="toc-anchor" href="#interactive-server-setup" title="Permanent link">&para;</a></h3>
<pre><code class="language-bash">bash setup/modules/optional-Modules/apps/freeipa-server.sh
</code></pre>
<p>Prompts for realm, domain, admin password, and whether to generate client-install scripts.</p>
<hr />
<h2 id="client-enrollment">Client Enrollment<a class="toc-anchor" href="#client-enrollment" title="Permanent link">&para;</a></h2>
<h3 id="via-installer-module">Via Installer Module<a class="toc-anchor" href="#via-installer-module" title="Permanent link">&para;</a></h3>
<p>Select <code>freeipa-client</code> during <code>tui-install.sh</code> or <code>install-modules.sh</code>.</p>
<h3 id="manual-enrollment">Manual Enrollment<a class="toc-anchor" href="#manual-enrollment" title="Permanent link">&para;</a></h3>
<p>Three modes:</p>
<pre><code class="language-bash"># Answerfile mode (unattended)
bash setup/modules/FreeipaAnsible/freeipa-client.sh \
--answerfile setup/modules/FreeipaAnsible/freeipa-client-answerfile.json
# Interactive prompts
bash setup/modules/FreeipaAnsible/freeipa-client.sh --interactive
# Direct flag passthrough to freeipa-enroll.sh
bash setup/modules/FreeipaAnsible/freeipa-client.sh \
--domain freeipa.example.com \
--server ipa.example.com \
--principal admin
</code></pre>
<h3 id="client-answerfile-schema">Client Answerfile Schema<a class="toc-anchor" href="#client-answerfile-schema" title="Permanent link">&para;</a></h3>
<pre><code class="language-json">{
&quot;domain&quot;: &quot;freeipa.abdelbaki.eu&quot;,
&quot;realm&quot;: &quot;FREEIPA.ABDELBAKI.EU&quot;,
&quot;server&quot;: &quot;freeipa.abdelbaki.eu&quot;,
&quot;hostname&quot;: &quot;&quot;,
&quot;principal&quot;: &quot;admin&quot;,
&quot;password&quot;: &quot;&quot;,
&quot;mkhomedir&quot;: true,
&quot;sudo&quot;: true,
&quot;dns_update&quot;: true,
&quot;ntp_server&quot;: &quot;&quot;,
&quot;fido2&quot;: false,
&quot;fido2_users&quot;: []
}
</code></pre>
<p>Leave <code>hostname</code> blank to use the current machine hostname. Leave <code>password</code> blank to be prompted at enrollment time.</p>
<hr />
<h2 id="ansible-playbooks">Ansible Playbooks<a class="toc-anchor" href="#ansible-playbooks" title="Permanent link">&para;</a></h2>
<p>All playbooks live in <code>setup/modules/FreeipaAnsible/ansible/</code> and require an inventory of enrolled IPA clients.</p>
<h3 id="deploy-package-auto-installer">Deploy Package Auto-Installer<a class="toc-anchor" href="#deploy-package-auto-installer" title="Permanent link">&para;</a></h3>
<pre><code class="language-bash">ansible-playbook -i inventory deploy-ansipa-install.yml
</code></pre>
<p>Deploys <code>ansipa-install-packages.sh</code> + a systemd timer that runs every 30 minutes. The script queries IPA for host groups named <code>ansipa-install-&lt;package&gt;</code> and installs/removes packages to match.</p>
<p><strong>Group naming convention:</strong> <code>ansipa-install-firefox</code> → installs the <code>firefox</code> package.</p>
<h3 id="deploy-module-auto-installer">Deploy Module Auto-Installer<a class="toc-anchor" href="#deploy-module-auto-installer" title="Permanent link">&para;</a></h3>
<pre><code class="language-bash">ansible-playbook -i inventory deploy-ansipa-modules.yml \
[-e ansipa_user=amir]
</code></pre>
<p>Deploys <code>ansipa-install-modules.sh</code> + timer. Queries for groups named <code>ansipa-module-&lt;name&gt;</code> and runs the matching script from <code>/usr/local/lib/ansipa-modules/&lt;name&gt;.sh</code>.</p>
<p>Module scripts are the same ones used by <code>install-modules.sh</code> — copied from <code>setup/modules/optional-Modules/apps/*.sh</code>.</p>
<p><strong>Group naming convention:</strong> <code>ansipa-module-docker</code> → runs <code>docker.sh</code> on the host.</p>
<p>Each module is applied once and stamped in <code>/var/lib/ansipa-modules/&lt;name&gt;.done</code>. Re-running the timer skips already-applied modules.</p>
<h3 id="deploy-baseuser-sync">Deploy BaseUser Sync<a class="toc-anchor" href="#deploy-baseuser-sync" title="Permanent link">&para;</a></h3>
<pre><code class="language-bash">ansible-playbook -i inventory deploy-baseuser-sync.yml
</code></pre>
<p>Deploys a <code>systemd.path</code> unit that triggers whenever a user logs in. If the user is a member of the IPA <code>BaseUser</code> group, they are automatically added to the local <code>baseusers</code> group — useful for desktop permission grants.</p>
<h3 id="collect-luks-backup-keys">Collect LUKS Backup Keys<a class="toc-anchor" href="#collect-luks-backup-keys" title="Permanent link">&para;</a></h3>
<pre><code class="language-bash">ansible-playbook -i inventory collect-luks-keys.yml \
[-e luks_keys_store=/secure/location]
</code></pre>
<p>For each enrolled host, checks for <code>/_LUKS_BACKUP_KEY</code> (placed there by the M-Archy installer when disk encryption is enabled) and fetches it to the controller as:</p>
<pre><code>&lt;luks_keys_store&gt;/&lt;HOSTNAME&gt;_LUKS_BACKUP_KEY
</code></pre>
<p>Keys are stored with mode <code>0400</code>. The store directory is created with mode <code>0700</code>.</p>
<p><strong>Schedule for automatic collection:</strong></p>
<pre><code class="language-bash"># Add to crontab on the Ansible controller
0 3 * * * cd /path/to/playbooks &amp;&amp; ansible-playbook -i inventory collect-luks-keys.yml
</code></pre>
<hr />
<h2 id="host-group-reference">Host Group Reference<a class="toc-anchor" href="#host-group-reference" title="Permanent link">&para;</a></h2>
<table>
<thead>
<tr>
<th>Group prefix</th>
<th>Handled by</th>
<th>Effect</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>ansipa-install-&lt;pkg&gt;</code></td>
<td><code>ansipa-install-packages.sh</code></td>
<td>Install/remove native package</td>
</tr>
<tr>
<td><code>ansipa-module-&lt;name&gt;</code></td>
<td><code>ansipa-install-modules.sh</code></td>
<td>Run module script once</td>
</tr>
<tr>
<td><code>fp_install-&lt;app&gt;</code></td>
<td><code>ansipa-install-flatpaks.sh</code></td>
<td>Install Flatpak app</td>
</tr>
<tr>
<td><code>BaseUser</code></td>
<td><code>auto-add-baseuser.sh</code></td>
<td>Add user to local <code>baseusers</code> group</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="luks-key-flow">LUKS Key Flow<a class="toc-anchor" href="#luks-key-flow" title="Permanent link">&para;</a></h2>
<pre><code> Install time (arch-autoinstall.sh or archbaseos-guided-install.sh)
─────────────────────────────────────────────────────────────────
1. User sets primary LUKS passphrase interactively
2. 64-byte random key generated from /dev/urandom
3. Key enrolled in second LUKS slot
4. Key written to /_LUKS_BACKUP_KEY (mode 0400, root-only)
inside the encrypted Btrfs volume
Post-install (Ansible)
──────────────────────
5. collect-luks-keys.yml runs from the controller
6. Fetches /_LUKS_BACKUP_KEY from each client
7. Stores as luks-keys/&lt;HOSTNAME&gt;_LUKS_BACKUP_KEY (mode 0400)
on the controller
</code></pre>
<p>The backup key lives inside the encrypted partition, so it is only accessible when the disk is already unlocked. Its purpose is to allow an admin to unlock the disk for recovery without knowing the user's passphrase.</p>
<hr />
<h2 id="auto-enrollment-ansible">Auto Enrollment + Ansible<a class="toc-anchor" href="#auto-enrollment-ansible" title="Permanent link">&para;</a></h2>
<pre><code class="language-bash">bash setup/modules/FreeipaAnsible/auto-enroll-ansible.sh
</code></pre>
<p>Combines FreeIPA client enrollment and Ansible deployment in one shot. Useful for provisioning scripts that run on first boot.</p>
</main>
<footer>
Generated by md-to-html.sh &nbsp;·&nbsp; CyberQueer theme &nbsp;·&nbsp; 2026-05-18
</footer>
</body>
</html>